mirror of
https://github.com/mjl-/mox.git
synced 2025-07-18 23:26:36 +03:00
imapserver: implement NOTIFY extension from RFC 5465
NOTIFY is like IDLE, but where IDLE watches just the selected mailbox, NOTIFY can watch all mailboxes. With NOTIFY, a client can also ask a server to immediately return configurable fetch attributes for new messages, e.g. a message preview, certain header fields, or simply the entire message. Mild testing with evolution and fairemail.
This commit is contained in:
@ -185,13 +185,18 @@ func (c *Conn) xflush() {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) xtrace(level slog.Level) func() {
|
||||
c.xflush()
|
||||
func (c *Conn) xtraceread(level slog.Level) func() {
|
||||
c.tr.SetTrace(level)
|
||||
return func() {
|
||||
c.tr.SetTrace(mlog.LevelTrace)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) xtracewrite(level slog.Level) func() {
|
||||
c.xflush()
|
||||
c.xtw.SetTrace(level)
|
||||
return func() {
|
||||
c.xflush()
|
||||
c.tr.SetTrace(mlog.LevelTrace)
|
||||
c.xtw.SetTrace(mlog.LevelTrace)
|
||||
}
|
||||
}
|
||||
@ -357,9 +362,10 @@ func (c *Conn) WriteSyncLiteral(s string) (untagged []Untagged, rerr error) {
|
||||
_, err = c.Readline()
|
||||
c.xcheckf(err, "read continuation line")
|
||||
|
||||
defer c.xtracewrite(mlog.LevelTracedata)()
|
||||
_, err = c.xbw.Write([]byte(s))
|
||||
c.xcheckf(err, "write literal data")
|
||||
c.xflush()
|
||||
c.xtracewrite(mlog.LevelTrace)
|
||||
return nil, nil
|
||||
}
|
||||
untagged, result, err := c.Response()
|
||||
|
@ -59,9 +59,9 @@ func (c *Conn) Login(username, password string) (untagged []Untagged, result Res
|
||||
|
||||
c.LastTag = c.nextTag()
|
||||
fmt.Fprintf(c.xbw, "%s login %s ", c.LastTag, astring(username))
|
||||
defer c.xtrace(mlog.LevelTraceauth)()
|
||||
defer c.xtracewrite(mlog.LevelTraceauth)()
|
||||
fmt.Fprintf(c.xbw, "%s\r\n", astring(password))
|
||||
c.xtrace(mlog.LevelTrace) // Restore.
|
||||
c.xtracewrite(mlog.LevelTrace) // Restore.
|
||||
return c.Response()
|
||||
}
|
||||
|
||||
@ -76,11 +76,11 @@ func (c *Conn) AuthenticatePlain(username, password string) (untagged []Untagged
|
||||
if result.Status != "" {
|
||||
c.xerrorf("got result status %q, expected continuation", result.Status)
|
||||
}
|
||||
defer c.xtrace(mlog.LevelTraceauth)()
|
||||
defer c.xtracewrite(mlog.LevelTraceauth)()
|
||||
xw := base64.NewEncoder(base64.StdEncoding, c.xbw)
|
||||
fmt.Fprintf(xw, "\u0000%s\u0000%s", username, password)
|
||||
xw.Close()
|
||||
c.xtrace(mlog.LevelTrace) // Restore.
|
||||
c.xtracewrite(mlog.LevelTrace) // Restore.
|
||||
fmt.Fprintf(c.xbw, "\r\n")
|
||||
c.xflush()
|
||||
return c.Response()
|
||||
@ -317,10 +317,10 @@ func (c *Conn) Append(mailbox string, message Append, more ...Append) (untagged
|
||||
// todo: for larger messages, use a synchronizing literal.
|
||||
|
||||
fmt.Fprintf(c.xbw, " (%s)%s {%d+}\r\n", strings.Join(m.Flags, " "), date, m.Size)
|
||||
defer c.xtrace(mlog.LevelTracedata)()
|
||||
defer c.xtracewrite(mlog.LevelTracedata)()
|
||||
_, err := io.Copy(c.xbw, m.Data)
|
||||
c.xcheckf(err, "write message data")
|
||||
c.xtrace(mlog.LevelTrace) // Restore
|
||||
c.xtracewrite(mlog.LevelTrace) // Restore
|
||||
}
|
||||
|
||||
fmt.Fprintf(c.xbw, "\r\n")
|
||||
@ -328,7 +328,8 @@ func (c *Conn) Append(mailbox string, message Append, more ...Append) (untagged
|
||||
return c.Response()
|
||||
}
|
||||
|
||||
// note: No idle command. Idle is better implemented by writing the request and reading and handling the responses as they come in.
|
||||
// note: No Idle or Notify command. Idle/Notify is better implemented by
|
||||
// writing the request and reading and handling the responses as they come in.
|
||||
|
||||
// CloseMailbox closes the currently selected/active mailbox, permanently removing
|
||||
// any messages marked with \Deleted.
|
||||
@ -444,10 +445,10 @@ func (c *Conn) replace(cmd string, num string, mailbox string, msg Append) (unta
|
||||
err := c.Commandf("", "%s %s %s (%s)%s ~{%d+}", cmd, num, astring(mailbox), strings.Join(msg.Flags, " "), date, msg.Size)
|
||||
c.xcheckf(err, "writing replace command")
|
||||
|
||||
defer c.xtrace(mlog.LevelTracedata)()
|
||||
defer c.xtracewrite(mlog.LevelTracedata)()
|
||||
_, err = io.Copy(c.xbw, msg.Data)
|
||||
c.xcheckf(err, "write message data")
|
||||
c.xtrace(mlog.LevelTrace)
|
||||
c.xtracewrite(mlog.LevelTrace)
|
||||
|
||||
fmt.Fprintf(c.xbw, "\r\n")
|
||||
c.xflush()
|
||||
|
@ -6,6 +6,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mjl-/mox/mlog"
|
||||
)
|
||||
|
||||
func (c *Conn) recorded() string {
|
||||
@ -131,7 +133,9 @@ var knownCodes = stringMap(
|
||||
// With parameters.
|
||||
"BADCHARSET", "CAPABILITY", "PERMANENTFLAGS", "UIDNEXT", "UIDVALIDITY", "UNSEEN", "APPENDUID", "COPYUID",
|
||||
"HIGHESTMODSEQ", "MODIFIED",
|
||||
"INPROGRESS", // ../rfc/9585:104
|
||||
"INPROGRESS", // ../rfc/9585:104
|
||||
"BADEVENT", "NOTIFICATIONOVERFLOW", // ../rfc/5465:1023
|
||||
"SERVERBUG",
|
||||
)
|
||||
|
||||
func stringMap(l ...string) map[string]struct{} {
|
||||
@ -247,6 +251,20 @@ func (c *Conn) xrespCode() (string, CodeArg) {
|
||||
c.xtake(")")
|
||||
}
|
||||
codeArg = CodeInProgress{tag, current, goal}
|
||||
case "BADEVENT":
|
||||
// ../rfc/5465:1033
|
||||
c.xspace()
|
||||
c.xtake("(")
|
||||
var l []string
|
||||
for {
|
||||
s := c.xatom()
|
||||
l = append(l, s)
|
||||
if !c.space() {
|
||||
break
|
||||
}
|
||||
}
|
||||
c.xtake(")")
|
||||
codeArg = CodeBadEvent(l)
|
||||
}
|
||||
return W, codeArg
|
||||
}
|
||||
@ -896,8 +914,10 @@ func (c *Conn) xliteral() []byte {
|
||||
c.xflush()
|
||||
}
|
||||
buf := make([]byte, int(size))
|
||||
defer c.xtraceread(mlog.LevelTracedata)()
|
||||
_, err := io.ReadFull(c.br, buf)
|
||||
c.xcheckf(err, "reading data for literal")
|
||||
c.xtraceread(mlog.LevelTrace)
|
||||
return buf
|
||||
}
|
||||
|
||||
|
@ -42,6 +42,7 @@ const (
|
||||
CapReplace Capability = "REPLACE" // ../rfc/8508:155
|
||||
CapPreview Capability = "PREVIEW" // ../rfc/8970:114
|
||||
CapMultiSearch Capability = "MULTISEARCH" // ../rfc/7377:187
|
||||
CapNotify Capability = "NOTIFY" // ../rfc/5465:195
|
||||
)
|
||||
|
||||
// Status is the tagged final result of a command.
|
||||
@ -186,6 +187,14 @@ func (c CodeInProgress) CodeString() string {
|
||||
return fmt.Sprintf("INPROGRESS (%q %s %s)", c.Tag, current, goal)
|
||||
}
|
||||
|
||||
// "BADEVENT" response code, with the events that are supported, for the NOTIFY
|
||||
// extension.
|
||||
type CodeBadEvent []string
|
||||
|
||||
func (c CodeBadEvent) CodeString() string {
|
||||
return fmt.Sprintf("BADEVENT (%s)", strings.Join([]string(c), " "))
|
||||
}
|
||||
|
||||
// RespText represents a response line minus the leading tag.
|
||||
type RespText struct {
|
||||
Code string // The first word between [] after the status.
|
||||
|
Reference in New Issue
Block a user