Skip to content

Commit 854c60b

Browse files
foxcppemersion
authored andcommitted
imapserver: Support RECENT for IMAP4rev1 servers
1 parent 805dce0 commit 854c60b

File tree

8 files changed

+74
-45
lines changed

8 files changed

+74
-45
lines changed

capability.go

+8
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,14 @@ func (set CapSet) has(c Cap) bool {
107107
return ok
108108
}
109109

110+
func (set CapSet) Copy() CapSet {
111+
newSet := make(CapSet, len(set))
112+
for c := range set {
113+
newSet[c] = struct{}{}
114+
}
115+
return newSet
116+
}
117+
110118
// Has checks whether a capability is supported.
111119
//
112120
// Some capabilities are implied by others, as such Has may return true even if

imapclient/status.go

+3
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ func (c *Client) Status(mailbox string, options *imap.StatusOptions) *StatusComm
3737
if options == nil {
3838
options = new(imap.StatusOptions)
3939
}
40+
if options.NumRecent {
41+
panic("StatusOptions.NumRecent is not supported in imapclient")
42+
}
4043

4144
cmd := &StatusCommand{mailbox: mailbox}
4245
enc := c.beginCommand("STATUS", cmd)

imapserver/conn.go

+15
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@ func (c *Conn) Bye(text string) error {
8585
return closeErr
8686
}
8787

88+
func (c *Conn) EnabledCaps() imap.CapSet {
89+
c.mutex.Lock()
90+
defer c.mutex.Unlock()
91+
92+
return c.enabled.Copy()
93+
}
94+
8895
func (c *Conn) serve() {
8996
defer func() {
9097
if v := recover(); v != nil {
@@ -585,6 +592,14 @@ func (w *UpdateWriter) WriteNumMessages(n uint32) error {
585592
return w.conn.writeExists(n)
586593
}
587594

595+
// WriteNumRecent writes an RECENT response (not used in IMAP4rev2, will be ignored).
596+
func (w *UpdateWriter) WriteNumRecent(n uint32) error {
597+
if w.conn.enabled.Has(imap.CapIMAP4rev2) || !w.conn.server.options.caps().Has(imap.CapIMAP4rev1) {
598+
return nil
599+
}
600+
return w.conn.writeObsoleteRecent(n)
601+
}
602+
588603
// WriteMailboxFlags writes a FLAGS response.
589604
func (w *UpdateWriter) WriteMailboxFlags(flags []imap.Flag) error {
590605
return w.conn.writeFlags(flags)

imapserver/list.go

+23-29
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
)
1111

1212
func (c *Conn) handleList(dec *imapwire.Decoder) error {
13-
ref, pattern, options, returnRecent, err := readListCmd(dec)
13+
ref, pattern, options, err := readListCmd(dec)
1414
if err != nil {
1515
return err
1616
}
@@ -20,9 +20,8 @@ func (c *Conn) handleList(dec *imapwire.Decoder) error {
2020
}
2121

2222
w := &ListWriter{
23-
conn: c,
24-
options: options,
25-
returnRecent: returnRecent,
23+
conn: c,
24+
options: options,
2625
}
2726
return c.session.List(w, ref, pattern, options)
2827
}
@@ -117,11 +116,11 @@ func (c *Conn) writeLSub(data *imap.ListData) error {
117116
return enc.CRLF()
118117
}
119118

120-
func readListCmd(dec *imapwire.Decoder) (ref string, patterns []string, options *imap.ListOptions, returnRecent bool, err error) {
119+
func readListCmd(dec *imapwire.Decoder) (ref string, patterns []string, options *imap.ListOptions, err error) {
121120
options = &imap.ListOptions{}
122121

123122
if !dec.ExpectSP() {
124-
return "", nil, nil, false, dec.Err()
123+
return "", nil, nil, dec.Err()
125124
}
126125

127126
hasSelectOpts, err := dec.List(func() error {
@@ -142,14 +141,14 @@ func readListCmd(dec *imapwire.Decoder) (ref string, patterns []string, options
142141
return nil
143142
})
144143
if err != nil {
145-
return "", nil, nil, false, fmt.Errorf("in list-select-opts: %w", err)
144+
return "", nil, nil, fmt.Errorf("in list-select-opts: %w", err)
146145
}
147146
if hasSelectOpts && !dec.ExpectSP() {
148-
return "", nil, nil, false, dec.Err()
147+
return "", nil, nil, dec.Err()
149148
}
150149

151150
if !dec.ExpectMailbox(&ref) || !dec.ExpectSP() {
152-
return "", nil, nil, false, dec.Err()
151+
return "", nil, nil, dec.Err()
153152
}
154153

155154
hasPatterns, err := dec.List(func() error {
@@ -160,13 +159,13 @@ func readListCmd(dec *imapwire.Decoder) (ref string, patterns []string, options
160159
return err
161160
})
162161
if err != nil {
163-
return "", nil, nil, false, err
162+
return "", nil, nil, err
164163
} else if hasPatterns && len(patterns) == 0 {
165-
return "", nil, nil, false, newClientBugError("LIST-EXTENDED requires a non-empty parenthesized pattern list")
164+
return "", nil, nil, newClientBugError("LIST-EXTENDED requires a non-empty parenthesized pattern list")
166165
} else if !hasPatterns {
167166
pattern, err := readListMailbox(dec)
168167
if err != nil {
169-
return "", nil, nil, false, err
168+
return "", nil, nil, err
170169
}
171170
if pattern != "" {
172171
patterns = append(patterns, pattern)
@@ -176,26 +175,26 @@ func readListCmd(dec *imapwire.Decoder) (ref string, patterns []string, options
176175
if dec.SP() { // list-return-opts
177176
var atom string
178177
if !dec.ExpectAtom(&atom) || !dec.Expect(strings.EqualFold(atom, "RETURN"), "RETURN") || !dec.ExpectSP() {
179-
return "", nil, nil, false, dec.Err()
178+
return "", nil, nil, dec.Err()
180179
}
181180

182181
err := dec.ExpectList(func() error {
183-
return readReturnOption(dec, options, &returnRecent)
182+
return readReturnOption(dec, options)
184183
})
185184
if err != nil {
186-
return "", nil, nil, false, fmt.Errorf("in list-return-opts: %w", err)
185+
return "", nil, nil, fmt.Errorf("in list-return-opts: %w", err)
187186
}
188187
}
189188

190189
if !dec.ExpectCRLF() {
191-
return "", nil, nil, false, dec.Err()
190+
return "", nil, nil, dec.Err()
192191
}
193192

194193
if options.SelectRecursiveMatch && !options.SelectSubscribed {
195-
return "", nil, nil, false, newClientBugError("The LIST RECURSIVEMATCH select option requires SUBSCRIBED")
194+
return "", nil, nil, newClientBugError("The LIST RECURSIVEMATCH select option requires SUBSCRIBED")
196195
}
197196

198-
return ref, patterns, options, returnRecent, nil
197+
return ref, patterns, options, nil
199198
}
200199

201200
func readListMailbox(dec *imapwire.Decoder) (string, error) {
@@ -219,7 +218,7 @@ func isListChar(ch byte) bool {
219218
}
220219
}
221220

222-
func readReturnOption(dec *imapwire.Decoder, options *imap.ListOptions, recent *bool) error {
221+
func readReturnOption(dec *imapwire.Decoder, options *imap.ListOptions) error {
223222
var name string
224223
if !dec.ExpectAtom(&name) {
225224
return dec.Err()
@@ -236,11 +235,7 @@ func readReturnOption(dec *imapwire.Decoder, options *imap.ListOptions, recent *
236235
}
237236
options.ReturnStatus = new(imap.StatusOptions)
238237
return dec.ExpectList(func() error {
239-
isRecent, err := readStatusItem(dec, options.ReturnStatus)
240-
if isRecent {
241-
*recent = true
242-
}
243-
return err
238+
return readStatusItem(dec, options.ReturnStatus)
244239
})
245240
default:
246241
return newClientBugError("Unknown LIST RETURN options")
@@ -250,10 +245,9 @@ func readReturnOption(dec *imapwire.Decoder, options *imap.ListOptions, recent *
250245

251246
// ListWriter writes LIST responses.
252247
type ListWriter struct {
253-
conn *Conn
254-
options *imap.ListOptions
255-
returnRecent bool
256-
lsub bool
248+
conn *Conn
249+
options *imap.ListOptions
250+
lsub bool
257251
}
258252

259253
// WriteList writes a single LIST response for a mailbox.
@@ -266,7 +260,7 @@ func (w *ListWriter) WriteList(data *imap.ListData) error {
266260
return err
267261
}
268262
if w.options.ReturnStatus != nil && data.Status != nil {
269-
if err := w.conn.writeStatus(data.Status, w.options.ReturnStatus, w.returnRecent); err != nil {
263+
if err := w.conn.writeStatus(data.Status, w.options.ReturnStatus); err != nil {
270264
return err
271265
}
272266
}

imapserver/select.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func (c *Conn) handleSelect(tag string, dec *imapwire.Decoder, readOnly bool) er
4242
return err
4343
}
4444
if !c.enabled.Has(imap.CapIMAP4rev2) {
45-
if err := c.writeObsoleteRecent(); err != nil {
45+
if err := c.writeObsoleteRecent(data.NumRecent); err != nil {
4646
return err
4747
}
4848
}
@@ -115,10 +115,10 @@ func (c *Conn) writeExists(numMessages uint32) error {
115115
return enc.Atom("*").SP().Number(numMessages).SP().Atom("EXISTS").CRLF()
116116
}
117117

118-
func (c *Conn) writeObsoleteRecent() error {
118+
func (c *Conn) writeObsoleteRecent(n uint32) error {
119119
enc := newResponseEncoder(c)
120120
defer enc.end()
121-
return enc.Atom("*").SP().Number(0).SP().Atom("RECENT").CRLF()
121+
return enc.Atom("*").SP().Number(n).SP().Atom("RECENT").CRLF()
122122
}
123123

124124
func (c *Conn) writeUIDValidity(uidValidity uint32) error {

imapserver/status.go

+17-13
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,10 @@ func (c *Conn) handleStatus(dec *imapwire.Decoder) error {
1414
}
1515

1616
var options imap.StatusOptions
17-
recent := false
1817
err := dec.ExpectList(func() error {
19-
isRecent, err := readStatusItem(dec, &options)
18+
err := readStatusItem(dec, &options)
2019
if err != nil {
2120
return err
22-
} else if isRecent {
23-
recent = true
2421
}
2522
return nil
2623
})
@@ -32,6 +29,13 @@ func (c *Conn) handleStatus(dec *imapwire.Decoder) error {
3229
return dec.Err()
3330
}
3431

32+
if options.NumRecent && !c.server.options.caps().Has(imap.CapIMAP4rev1) {
33+
return &imap.Error{
34+
Type: imap.StatusResponseTypeBad,
35+
Text: "Unknown STATUS data item",
36+
}
37+
}
38+
3539
if err := c.checkState(imap.ConnStateAuthenticated); err != nil {
3640
return err
3741
}
@@ -41,10 +45,10 @@ func (c *Conn) handleStatus(dec *imapwire.Decoder) error {
4145
return err
4246
}
4347

44-
return c.writeStatus(data, &options, recent)
48+
return c.writeStatus(data, &options)
4549
}
4650

47-
func (c *Conn) writeStatus(data *imap.StatusData, options *imap.StatusOptions, recent bool) error {
51+
func (c *Conn) writeStatus(data *imap.StatusData, options *imap.StatusOptions) error {
4852
enc := newResponseEncoder(c)
4953
defer enc.end()
5054

@@ -79,18 +83,18 @@ func (c *Conn) writeStatus(data *imap.StatusData, options *imap.StatusOptions, r
7983
if options.DeletedStorage {
8084
listEnc.Item().Atom("DELETED-STORAGE").SP().Number64(*data.DeletedStorage)
8185
}
82-
if recent {
83-
listEnc.Item().Atom("RECENT").SP().Number(0)
86+
if options.NumRecent {
87+
listEnc.Item().Atom("RECENT").SP().Number(*data.NumRecent)
8488
}
8589
listEnc.End()
8690

8791
return enc.CRLF()
8892
}
8993

90-
func readStatusItem(dec *imapwire.Decoder, options *imap.StatusOptions) (isRecent bool, err error) {
94+
func readStatusItem(dec *imapwire.Decoder, options *imap.StatusOptions) error {
9195
var name string
9296
if !dec.ExpectAtom(&name) {
93-
return false, dec.Err()
97+
return dec.Err()
9498
}
9599
switch strings.ToUpper(name) {
96100
case "MESSAGES":
@@ -110,12 +114,12 @@ func readStatusItem(dec *imapwire.Decoder, options *imap.StatusOptions) (isRecen
110114
case "DELETED-STORAGE":
111115
options.DeletedStorage = true
112116
case "RECENT":
113-
isRecent = true
117+
options.NumRecent = true
114118
default:
115-
return false, &imap.Error{
119+
return &imap.Error{
116120
Type: imap.StatusResponseTypeBad,
117121
Text: "Unknown STATUS data item",
118122
}
119123
}
120-
return isRecent, nil
124+
return nil
121125
}

select.go

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ type SelectData struct {
1616
PermanentFlags []Flag
1717
// Number of messages in this mailbox (aka. "EXISTS")
1818
NumMessages uint32
19+
// Number of recent messages in this mailbox ("RECENT") (Obsolete, IMAP4rev1 only).
20+
// Server-only, not supported in imapclient.
21+
NumRecent uint32
1922
UIDNext UID
2023
UIDValidity uint32
2124

status.go

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package imap
33
// StatusOptions contains options for the STATUS command.
44
type StatusOptions struct {
55
NumMessages bool
6+
NumRecent bool // Obsolete, IMAP4rev1 only. Server-only, not supported in imapclient.
67
UIDNext bool
78
UIDValidity bool
89
NumUnseen bool
@@ -21,6 +22,7 @@ type StatusData struct {
2122
Mailbox string
2223

2324
NumMessages *uint32
25+
NumRecent *uint32 // Obsolete, IMAP4rev1 only. Server-only, not supported in imapclient.
2426
UIDNext UID
2527
UIDValidity uint32
2628
NumUnseen *uint32

0 commit comments

Comments
 (0)