diff --git a/imapserver/conn.go b/imapserver/conn.go index e3d8179b..e045f086 100644 --- a/imapserver/conn.go +++ b/imapserver/conn.go @@ -202,7 +202,10 @@ func (c *Conn) readCommand(dec *imapwire.Decoder) error { // TODO: handle multiple commands concurrently sendOK := true - var err error + var ( + err error + cmd *command + ) switch name { case "NOOP", "CHECK": err = c.handleNoop(dec) @@ -252,7 +255,7 @@ func (c *Conn) readCommand(dec *imapwire.Decoder) error { err = c.handleAppend(tag, dec) sendOK = false case "FETCH", "UID FETCH": - err = c.handleFetch(dec, numKind) + cmd, err = c.handleFetch(dec, numKind) case "EXPUNGE": err = c.handleExpunge(dec) case "UID EXPUNGE": @@ -282,6 +285,10 @@ func (c *Conn) readCommand(dec *imapwire.Decoder) error { dec.DiscardLine() + if err == nil && cmd != nil { + err = cmd.Wait() + } + var ( resp *imap.StatusResponse imapErr *imap.Error @@ -474,6 +481,32 @@ func (c *Conn) poll(cmd string) error { return c.session.Poll(w, allowExpunge) } +type command struct { + done chan struct{} + err error +} + +func newCommand(f func() error) *command { + cmd := &command{done: make(chan struct{})} + go func() { + var err error + defer func() { + if v := recover(); v != nil { + err = fmt.Errorf("panic handling command: %v\n%s", v, debug.Stack()) + } + cmd.err = err + close(cmd.done) + }() + err = f() + }() + return cmd +} + +func (cmd *command) Wait() error { + <-cmd.done + return cmd.err +} + type responseEncoder struct { *imapwire.Encoder conn *Conn diff --git a/imapserver/fetch.go b/imapserver/fetch.go index cefd4287..ee5d9eba 100644 --- a/imapserver/fetch.go +++ b/imapserver/fetch.go @@ -23,10 +23,10 @@ type fetchWriterOptions struct { obsolete map[*imap.FetchItemBodySection]string } -func (c *Conn) handleFetch(dec *imapwire.Decoder, numKind NumKind) error { +func (c *Conn) handleFetch(dec *imapwire.Decoder, numKind NumKind) (*command, error) { var seqSet imap.SeqSet if !dec.ExpectSP() || !dec.ExpectSeqSet(&seqSet) || !dec.ExpectSP() { - return dec.Err() + return nil, dec.Err() } var options imap.FetchOptions @@ -43,12 +43,12 @@ func (c *Conn) handleFetch(dec *imapwire.Decoder, numKind NumKind) error { return handleFetchAtt(dec, name, &options, &writerOptions) }) if err != nil { - return err + return nil, err } if !isList { name, err := readFetchAttName(dec) if err != nil { - return err + return nil, err } // Handle macros @@ -70,17 +70,17 @@ func (c *Conn) handleFetch(dec *imapwire.Decoder, numKind NumKind) error { handleFetchBodyStructure(&options, &writerOptions, false) default: if err := handleFetchAtt(dec, name, &options, &writerOptions); err != nil { - return err + return nil, err } } } if !dec.ExpectCRLF() { - return dec.Err() + return nil, dec.Err() } if err := c.checkState(imap.ConnStateSelected); err != nil { - return err + return nil, err } if numKind == NumKindUID { @@ -88,10 +88,9 @@ func (c *Conn) handleFetch(dec *imapwire.Decoder, numKind NumKind) error { } w := &FetchWriter{conn: c, options: writerOptions} - if err := c.session.Fetch(w, numKind, seqSet, &options); err != nil { - return err - } - return nil + return newCommand(func() error { + return c.session.Fetch(w, numKind, seqSet, &options) + }), nil } func handleFetchAtt(dec *imapwire.Decoder, attName string, options *imap.FetchOptions, writerOptions *fetchWriterOptions) error {