This is the v1 version, had the v2 before.
This commit is contained in:
61
responses/authenticate.go
Normal file
61
responses/authenticate.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-sasl"
|
||||
)
|
||||
|
||||
// An AUTHENTICATE response.
|
||||
type Authenticate struct {
|
||||
Mechanism sasl.Client
|
||||
InitialResponse []byte
|
||||
RepliesCh chan []byte
|
||||
}
|
||||
|
||||
// Implements
|
||||
func (r *Authenticate) Replies() <-chan []byte {
|
||||
return r.RepliesCh
|
||||
}
|
||||
|
||||
func (r *Authenticate) writeLine(l string) error {
|
||||
r.RepliesCh <- []byte(l + "\r\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Authenticate) cancel() error {
|
||||
return r.writeLine("*")
|
||||
}
|
||||
|
||||
func (r *Authenticate) Handle(resp imap.Resp) error {
|
||||
cont, ok := resp.(*imap.ContinuationReq)
|
||||
if !ok {
|
||||
return ErrUnhandled
|
||||
}
|
||||
|
||||
// Empty challenge, send initial response as stated in RFC 2222 section 5.1
|
||||
if cont.Info == "" && r.InitialResponse != nil {
|
||||
encoded := base64.StdEncoding.EncodeToString(r.InitialResponse)
|
||||
if err := r.writeLine(encoded); err != nil {
|
||||
return err
|
||||
}
|
||||
r.InitialResponse = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
challenge, err := base64.StdEncoding.DecodeString(cont.Info)
|
||||
if err != nil {
|
||||
r.cancel()
|
||||
return err
|
||||
}
|
||||
|
||||
reply, err := r.Mechanism.Next(challenge)
|
||||
if err != nil {
|
||||
r.cancel()
|
||||
return err
|
||||
}
|
||||
|
||||
encoded := base64.StdEncoding.EncodeToString(reply)
|
||||
return r.writeLine(encoded)
|
||||
}
|
||||
20
responses/capability.go
Normal file
20
responses/capability.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// A CAPABILITY response.
|
||||
// See RFC 3501 section 7.2.1
|
||||
type Capability struct {
|
||||
Caps []string
|
||||
}
|
||||
|
||||
func (r *Capability) WriteTo(w *imap.Writer) error {
|
||||
fields := []interface{}{imap.RawString("CAPABILITY")}
|
||||
for _, cap := range r.Caps {
|
||||
fields = append(fields, imap.RawString(cap))
|
||||
}
|
||||
|
||||
return imap.NewUntaggedResp(fields).WriteTo(w)
|
||||
}
|
||||
33
responses/enabled.go
Normal file
33
responses/enabled.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// An ENABLED response, defined in RFC 5161 section 3.2.
|
||||
type Enabled struct {
|
||||
Caps []string
|
||||
}
|
||||
|
||||
func (r *Enabled) Handle(resp imap.Resp) error {
|
||||
name, fields, ok := imap.ParseNamedResp(resp)
|
||||
if !ok || name != "ENABLED" {
|
||||
return ErrUnhandled
|
||||
}
|
||||
|
||||
if caps, err := imap.ParseStringList(fields); err != nil {
|
||||
return err
|
||||
} else {
|
||||
r.Caps = append(r.Caps, caps...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Enabled) WriteTo(w *imap.Writer) error {
|
||||
fields := []interface{}{imap.RawString("ENABLED")}
|
||||
for _, cap := range r.Caps {
|
||||
fields = append(fields, imap.RawString(cap))
|
||||
}
|
||||
return imap.NewUntaggedResp(fields).WriteTo(w)
|
||||
}
|
||||
43
responses/expunge.go
Normal file
43
responses/expunge.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
const expungeName = "EXPUNGE"
|
||||
|
||||
// An EXPUNGE response.
|
||||
// See RFC 3501 section 7.4.1
|
||||
type Expunge struct {
|
||||
SeqNums chan uint32
|
||||
}
|
||||
|
||||
func (r *Expunge) Handle(resp imap.Resp) error {
|
||||
name, fields, ok := imap.ParseNamedResp(resp)
|
||||
if !ok || name != expungeName {
|
||||
return ErrUnhandled
|
||||
}
|
||||
|
||||
if len(fields) == 0 {
|
||||
return errNotEnoughFields
|
||||
}
|
||||
|
||||
seqNum, err := imap.ParseNumber(fields[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.SeqNums <- seqNum
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Expunge) WriteTo(w *imap.Writer) error {
|
||||
for seqNum := range r.SeqNums {
|
||||
resp := imap.NewUntaggedResp([]interface{}{seqNum, imap.RawString(expungeName)})
|
||||
if err := resp.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
70
responses/fetch.go
Normal file
70
responses/fetch.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
const fetchName = "FETCH"
|
||||
|
||||
// A FETCH response.
|
||||
// See RFC 3501 section 7.4.2
|
||||
type Fetch struct {
|
||||
Messages chan *imap.Message
|
||||
SeqSet *imap.SeqSet
|
||||
Uid bool
|
||||
}
|
||||
|
||||
func (r *Fetch) Handle(resp imap.Resp) error {
|
||||
name, fields, ok := imap.ParseNamedResp(resp)
|
||||
if !ok || name != fetchName {
|
||||
return ErrUnhandled
|
||||
} else if len(fields) < 1 {
|
||||
return errNotEnoughFields
|
||||
}
|
||||
|
||||
seqNum, err := imap.ParseNumber(fields[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msgFields, _ := fields[1].([]interface{})
|
||||
msg := &imap.Message{SeqNum: seqNum}
|
||||
if err := msg.Parse(msgFields); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if r.Uid && msg.Uid == 0 {
|
||||
// we requested UIDs and got a message without one --> unilateral update --> ignore
|
||||
return ErrUnhandled
|
||||
}
|
||||
|
||||
var num uint32
|
||||
if r.Uid {
|
||||
num = msg.Uid
|
||||
} else {
|
||||
num = seqNum
|
||||
}
|
||||
|
||||
// Check whether we obtained a result we requested with our SeqSet
|
||||
// If the result is not contained in our SeqSet we have to handle an additional special case:
|
||||
// In case we requested UIDs with a dynamic sequence (i.e. * or n:*) and the maximum UID of the mailbox
|
||||
// is less then our n, the server will supply us with the max UID (cf. RFC 3501 §6.4.8 and §9 `seq-range`).
|
||||
// Thus, such a result is correct and has to be returned by us.
|
||||
if !r.SeqSet.Contains(num) && (!r.Uid || !r.SeqSet.Dynamic()) {
|
||||
return ErrUnhandled
|
||||
}
|
||||
|
||||
r.Messages <- msg
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Fetch) WriteTo(w *imap.Writer) error {
|
||||
var err error
|
||||
for msg := range r.Messages {
|
||||
resp := imap.NewUntaggedResp([]interface{}{msg.SeqNum, imap.RawString(fetchName), msg.Format()})
|
||||
if err == nil {
|
||||
err = resp.WriteTo(w)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
38
responses/idle.go
Normal file
38
responses/idle.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// An IDLE response.
|
||||
type Idle struct {
|
||||
RepliesCh chan []byte
|
||||
Stop <-chan struct{}
|
||||
|
||||
gotContinuationReq bool
|
||||
}
|
||||
|
||||
func (r *Idle) Replies() <-chan []byte {
|
||||
return r.RepliesCh
|
||||
}
|
||||
|
||||
func (r *Idle) stop() {
|
||||
r.RepliesCh <- []byte("DONE\r\n")
|
||||
}
|
||||
|
||||
func (r *Idle) Handle(resp imap.Resp) error {
|
||||
// Wait for a continuation request
|
||||
if _, ok := resp.(*imap.ContinuationReq); ok && !r.gotContinuationReq {
|
||||
r.gotContinuationReq = true
|
||||
|
||||
// We got a continuation request, wait for r.Stop to be closed
|
||||
go func() {
|
||||
<-r.Stop
|
||||
r.stop()
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return ErrUnhandled
|
||||
}
|
||||
57
responses/list.go
Normal file
57
responses/list.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
const (
|
||||
listName = "LIST"
|
||||
lsubName = "LSUB"
|
||||
)
|
||||
|
||||
// A LIST response.
|
||||
// If Subscribed is set to true, LSUB will be used instead.
|
||||
// See RFC 3501 section 7.2.2
|
||||
type List struct {
|
||||
Mailboxes chan *imap.MailboxInfo
|
||||
Subscribed bool
|
||||
}
|
||||
|
||||
func (r *List) Name() string {
|
||||
if r.Subscribed {
|
||||
return lsubName
|
||||
} else {
|
||||
return listName
|
||||
}
|
||||
}
|
||||
|
||||
func (r *List) Handle(resp imap.Resp) error {
|
||||
name, fields, ok := imap.ParseNamedResp(resp)
|
||||
if !ok || name != r.Name() {
|
||||
return ErrUnhandled
|
||||
}
|
||||
|
||||
mbox := &imap.MailboxInfo{}
|
||||
if err := mbox.Parse(fields); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.Mailboxes <- mbox
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *List) WriteTo(w *imap.Writer) error {
|
||||
respName := r.Name()
|
||||
|
||||
for mbox := range r.Mailboxes {
|
||||
fields := []interface{}{imap.RawString(respName)}
|
||||
fields = append(fields, mbox.Format()...)
|
||||
|
||||
resp := imap.NewUntaggedResp(fields)
|
||||
if err := resp.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
65
responses/list_test.go
Normal file
65
responses/list_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
func TestListSlashDelimiter(t *testing.T) {
|
||||
mbox := &imap.MailboxInfo{}
|
||||
|
||||
if err := mbox.Parse([]interface{}{
|
||||
[]interface{}{"\\Unseen"},
|
||||
"/",
|
||||
"INBOX",
|
||||
}); err != nil {
|
||||
t.Error(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
if response := getListResponse(t, mbox); response != `* LIST (\Unseen) "/" INBOX`+"\r\n" {
|
||||
t.Error("Unexpected response:", response)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListNILDelimiter(t *testing.T) {
|
||||
mbox := &imap.MailboxInfo{}
|
||||
|
||||
if err := mbox.Parse([]interface{}{
|
||||
[]interface{}{"\\Unseen"},
|
||||
nil,
|
||||
"INBOX",
|
||||
}); err != nil {
|
||||
t.Error(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
if response := getListResponse(t, mbox); response != `* LIST (\Unseen) NIL INBOX`+"\r\n" {
|
||||
t.Error("Unexpected response:", response)
|
||||
}
|
||||
}
|
||||
|
||||
func newListResponse(mbox *imap.MailboxInfo) (l *List) {
|
||||
l = &List{Mailboxes: make(chan *imap.MailboxInfo)}
|
||||
|
||||
go func() {
|
||||
l.Mailboxes <- mbox
|
||||
close(l.Mailboxes)
|
||||
}()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getListResponse(t *testing.T, mbox *imap.MailboxInfo) string {
|
||||
b := &bytes.Buffer{}
|
||||
w := imap.NewWriter(b)
|
||||
|
||||
if err := newListResponse(mbox).WriteTo(w); err != nil {
|
||||
t.Error(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
35
responses/responses.go
Normal file
35
responses/responses.go
Normal file
@@ -0,0 +1,35 @@
|
||||
// IMAP responses defined in RFC 3501.
|
||||
package responses
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// ErrUnhandled is used when a response hasn't been handled.
|
||||
var ErrUnhandled = errors.New("imap: unhandled response")
|
||||
|
||||
var errNotEnoughFields = errors.New("imap: not enough fields in response")
|
||||
|
||||
// Handler handles responses.
|
||||
type Handler interface {
|
||||
// Handle processes a response. If the response cannot be processed,
|
||||
// ErrUnhandledResp must be returned.
|
||||
Handle(resp imap.Resp) error
|
||||
}
|
||||
|
||||
// HandlerFunc is a function that handles responses.
|
||||
type HandlerFunc func(resp imap.Resp) error
|
||||
|
||||
// Handle implements Handler.
|
||||
func (f HandlerFunc) Handle(resp imap.Resp) error {
|
||||
return f(resp)
|
||||
}
|
||||
|
||||
// Replier is a Handler that needs to send raw data (for instance
|
||||
// AUTHENTICATE).
|
||||
type Replier interface {
|
||||
Handler
|
||||
Replies() <-chan []byte
|
||||
}
|
||||
41
responses/search.go
Normal file
41
responses/search.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
const searchName = "SEARCH"
|
||||
|
||||
// A SEARCH response.
|
||||
// See RFC 3501 section 7.2.5
|
||||
type Search struct {
|
||||
Ids []uint32
|
||||
}
|
||||
|
||||
func (r *Search) Handle(resp imap.Resp) error {
|
||||
name, fields, ok := imap.ParseNamedResp(resp)
|
||||
if !ok || name != searchName {
|
||||
return ErrUnhandled
|
||||
}
|
||||
|
||||
r.Ids = make([]uint32, len(fields))
|
||||
for i, f := range fields {
|
||||
if id, err := imap.ParseNumber(f); err != nil {
|
||||
return err
|
||||
} else {
|
||||
r.Ids[i] = id
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Search) WriteTo(w *imap.Writer) (err error) {
|
||||
fields := []interface{}{imap.RawString(searchName)}
|
||||
for _, id := range r.Ids {
|
||||
fields = append(fields, id)
|
||||
}
|
||||
|
||||
resp := imap.NewUntaggedResp(fields)
|
||||
return resp.WriteTo(w)
|
||||
}
|
||||
142
responses/select.go
Normal file
142
responses/select.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
)
|
||||
|
||||
// A SELECT response.
|
||||
type Select struct {
|
||||
Mailbox *imap.MailboxStatus
|
||||
}
|
||||
|
||||
func (r *Select) Handle(resp imap.Resp) error {
|
||||
if r.Mailbox == nil {
|
||||
r.Mailbox = &imap.MailboxStatus{Items: make(map[imap.StatusItem]interface{})}
|
||||
}
|
||||
mbox := r.Mailbox
|
||||
|
||||
switch resp := resp.(type) {
|
||||
case *imap.DataResp:
|
||||
name, fields, ok := imap.ParseNamedResp(resp)
|
||||
if !ok || name != "FLAGS" {
|
||||
return ErrUnhandled
|
||||
} else if len(fields) < 1 {
|
||||
return errNotEnoughFields
|
||||
}
|
||||
|
||||
flags, _ := fields[0].([]interface{})
|
||||
mbox.Flags, _ = imap.ParseStringList(flags)
|
||||
case *imap.StatusResp:
|
||||
if len(resp.Arguments) < 1 {
|
||||
return ErrUnhandled
|
||||
}
|
||||
|
||||
var item imap.StatusItem
|
||||
switch resp.Code {
|
||||
case "UNSEEN":
|
||||
mbox.UnseenSeqNum, _ = imap.ParseNumber(resp.Arguments[0])
|
||||
case "PERMANENTFLAGS":
|
||||
flags, _ := resp.Arguments[0].([]interface{})
|
||||
mbox.PermanentFlags, _ = imap.ParseStringList(flags)
|
||||
case "UIDNEXT":
|
||||
mbox.UidNext, _ = imap.ParseNumber(resp.Arguments[0])
|
||||
item = imap.StatusUidNext
|
||||
case "UIDVALIDITY":
|
||||
mbox.UidValidity, _ = imap.ParseNumber(resp.Arguments[0])
|
||||
item = imap.StatusUidValidity
|
||||
default:
|
||||
return ErrUnhandled
|
||||
}
|
||||
|
||||
if item != "" {
|
||||
mbox.ItemsLocker.Lock()
|
||||
mbox.Items[item] = nil
|
||||
mbox.ItemsLocker.Unlock()
|
||||
}
|
||||
default:
|
||||
return ErrUnhandled
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Select) WriteTo(w *imap.Writer) error {
|
||||
mbox := r.Mailbox
|
||||
|
||||
if mbox.Flags != nil {
|
||||
flags := make([]interface{}, len(mbox.Flags))
|
||||
for i, f := range mbox.Flags {
|
||||
flags[i] = imap.RawString(f)
|
||||
}
|
||||
res := imap.NewUntaggedResp([]interface{}{imap.RawString("FLAGS"), flags})
|
||||
if err := res.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if mbox.PermanentFlags != nil {
|
||||
flags := make([]interface{}, len(mbox.PermanentFlags))
|
||||
for i, f := range mbox.PermanentFlags {
|
||||
flags[i] = imap.RawString(f)
|
||||
}
|
||||
statusRes := &imap.StatusResp{
|
||||
Type: imap.StatusRespOk,
|
||||
Code: imap.CodePermanentFlags,
|
||||
Arguments: []interface{}{flags},
|
||||
Info: "Flags permitted.",
|
||||
}
|
||||
if err := statusRes.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if mbox.UnseenSeqNum > 0 {
|
||||
statusRes := &imap.StatusResp{
|
||||
Type: imap.StatusRespOk,
|
||||
Code: imap.CodeUnseen,
|
||||
Arguments: []interface{}{mbox.UnseenSeqNum},
|
||||
Info: fmt.Sprintf("Message %d is first unseen", mbox.UnseenSeqNum),
|
||||
}
|
||||
if err := statusRes.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for k := range r.Mailbox.Items {
|
||||
switch k {
|
||||
case imap.StatusMessages:
|
||||
res := imap.NewUntaggedResp([]interface{}{mbox.Messages, imap.RawString("EXISTS")})
|
||||
if err := res.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
case imap.StatusRecent:
|
||||
res := imap.NewUntaggedResp([]interface{}{mbox.Recent, imap.RawString("RECENT")})
|
||||
if err := res.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
case imap.StatusUidNext:
|
||||
statusRes := &imap.StatusResp{
|
||||
Type: imap.StatusRespOk,
|
||||
Code: imap.CodeUidNext,
|
||||
Arguments: []interface{}{mbox.UidNext},
|
||||
Info: "Predicted next UID",
|
||||
}
|
||||
if err := statusRes.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
case imap.StatusUidValidity:
|
||||
statusRes := &imap.StatusResp{
|
||||
Type: imap.StatusRespOk,
|
||||
Code: imap.CodeUidValidity,
|
||||
Arguments: []interface{}{mbox.UidValidity},
|
||||
Info: "UIDs valid",
|
||||
}
|
||||
if err := statusRes.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
53
responses/status.go
Normal file
53
responses/status.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/utf7"
|
||||
)
|
||||
|
||||
const statusName = "STATUS"
|
||||
|
||||
// A STATUS response.
|
||||
// See RFC 3501 section 7.2.4
|
||||
type Status struct {
|
||||
Mailbox *imap.MailboxStatus
|
||||
}
|
||||
|
||||
func (r *Status) Handle(resp imap.Resp) error {
|
||||
if r.Mailbox == nil {
|
||||
r.Mailbox = &imap.MailboxStatus{}
|
||||
}
|
||||
mbox := r.Mailbox
|
||||
|
||||
name, fields, ok := imap.ParseNamedResp(resp)
|
||||
if !ok || name != statusName {
|
||||
return ErrUnhandled
|
||||
} else if len(fields) < 2 {
|
||||
return errNotEnoughFields
|
||||
}
|
||||
|
||||
if name, err := imap.ParseString(fields[0]); err != nil {
|
||||
return err
|
||||
} else if name, err := utf7.Encoding.NewDecoder().String(name); err != nil {
|
||||
return err
|
||||
} else {
|
||||
mbox.Name = imap.CanonicalMailboxName(name)
|
||||
}
|
||||
|
||||
var items []interface{}
|
||||
if items, ok = fields[1].([]interface{}); !ok {
|
||||
return errors.New("STATUS response expects a list as second argument")
|
||||
}
|
||||
|
||||
mbox.Items = nil
|
||||
return mbox.Parse(items)
|
||||
}
|
||||
|
||||
func (r *Status) WriteTo(w *imap.Writer) error {
|
||||
mbox := r.Mailbox
|
||||
name, _ := utf7.Encoding.NewEncoder().String(mbox.Name)
|
||||
fields := []interface{}{imap.RawString(statusName), imap.FormatMailboxName(name), mbox.Format()}
|
||||
return imap.NewUntaggedResp(fields).WriteTo(w)
|
||||
}
|
||||
Reference in New Issue
Block a user