Skip to content

Commit 795b28a

Browse files
authored
Ignore unilateral updates from the server in FETCH
1 parent 6159a01 commit 795b28a

File tree

3 files changed

+148
-2
lines changed

3 files changed

+148
-2
lines changed

client/cmd_selected.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ func (c *Client) fetch(uid bool, seqset *imap.SeqSet, items []imap.FetchItem, ch
152152
cmd = &commands.Uid{Cmd: cmd}
153153
}
154154

155-
res := &responses.Fetch{Messages: ch}
155+
res := &responses.Fetch{Messages: ch, SeqSet: seqset, Uid: uid}
156156

157157
status, err := c.execute(cmd, res)
158158
if err != nil {
@@ -211,7 +211,7 @@ func (c *Client) store(uid bool, seqset *imap.SeqSet, item imap.StoreItem, value
211211

212212
var h responses.Handler
213213
if ch != nil {
214-
h = &responses.Fetch{Messages: ch}
214+
h = &responses.Fetch{Messages: ch, SeqSet: seqset, Uid: uid}
215215
}
216216

217217
status, err := c.execute(cmd, h)

client/cmd_selected_test.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,129 @@ func TestClient_Fetch_Uid(t *testing.T) {
378378
}
379379
}
380380

381+
func TestClient_Fetch_Unilateral(t *testing.T) {
382+
c, s := newTestClient(t)
383+
defer s.Close()
384+
385+
setClientState(c, imap.SelectedState, nil)
386+
387+
seqset, _ := imap.ParseSeqSet("1:4")
388+
fields := []imap.FetchItem{imap.FetchFlags}
389+
390+
done := make(chan error, 1)
391+
messages := make(chan *imap.Message, 3)
392+
go func() {
393+
done <- c.Fetch(seqset, fields, messages)
394+
}()
395+
396+
tag, cmd := s.ScanCmd()
397+
if cmd != "FETCH 1:4 (FLAGS)" {
398+
t.Fatalf("client sent command %v, want %v", cmd, "FETCH 1:4 (FLAGS)")
399+
}
400+
401+
s.WriteString("* 2 FETCH (FLAGS (\\Seen))\r\n")
402+
s.WriteString("* 123 FETCH (FLAGS (\\Deleted))\r\n")
403+
s.WriteString("* 4 FETCH (FLAGS (\\Seen))\r\n")
404+
s.WriteString(tag + " OK FETCH completed\r\n")
405+
406+
if err := <-done; err != nil {
407+
t.Fatalf("c.Fetch() = %v", err)
408+
}
409+
410+
msg := <-messages
411+
if msg.SeqNum != 2 {
412+
t.Errorf("First message has bad sequence number: %v", msg.SeqNum)
413+
}
414+
msg = <-messages
415+
if msg.SeqNum != 4 {
416+
t.Errorf("Second message has bad sequence number: %v", msg.SeqNum)
417+
}
418+
419+
_, ok := <-messages
420+
if ok {
421+
t.Errorf("More than two messages")
422+
}
423+
}
424+
425+
func TestClient_Fetch_Unilateral_Uid(t *testing.T) {
426+
c, s := newTestClient(t)
427+
defer s.Close()
428+
429+
setClientState(c, imap.SelectedState, nil)
430+
431+
seqset, _ := imap.ParseSeqSet("1:4")
432+
fields := []imap.FetchItem{imap.FetchFlags}
433+
434+
done := make(chan error, 1)
435+
messages := make(chan *imap.Message, 3)
436+
go func() {
437+
done <- c.UidFetch(seqset, fields, messages)
438+
}()
439+
440+
tag, cmd := s.ScanCmd()
441+
if cmd != "UID FETCH 1:4 (FLAGS)" {
442+
t.Fatalf("client sent command %v, want %v", cmd, "UID FETCH 1:4 (FLAGS)")
443+
}
444+
445+
s.WriteString("* 23 FETCH (UID 2 FLAGS (\\Seen))\r\n")
446+
s.WriteString("* 123 FETCH (FLAGS (\\Deleted))\r\n")
447+
s.WriteString("* 49 FETCH (UID 4 FLAGS (\\Seen))\r\n")
448+
s.WriteString(tag + " OK FETCH completed\r\n")
449+
450+
if err := <-done; err != nil {
451+
t.Fatalf("c.Fetch() = %v", err)
452+
}
453+
454+
msg := <-messages
455+
if msg.Uid != 2 {
456+
t.Errorf("First message has bad UID: %v", msg.Uid)
457+
}
458+
msg = <-messages
459+
if msg.Uid != 4 {
460+
t.Errorf("Second message has bad UID: %v", msg.Uid)
461+
}
462+
463+
_, ok := <-messages
464+
if ok {
465+
t.Errorf("More than two messages")
466+
}
467+
}
468+
469+
func TestClient_Fetch_Uid_Dynamic(t *testing.T) {
470+
c, s := newTestClient(t)
471+
defer s.Close()
472+
473+
setClientState(c, imap.SelectedState, nil)
474+
475+
seqset, _ := imap.ParseSeqSet("4:*")
476+
fields := []imap.FetchItem{imap.FetchFlags}
477+
478+
done := make(chan error, 1)
479+
messages := make(chan *imap.Message, 1)
480+
go func() {
481+
done <- c.UidFetch(seqset, fields, messages)
482+
}()
483+
484+
tag, cmd := s.ScanCmd()
485+
if cmd != "UID FETCH 4:* (FLAGS)" {
486+
t.Fatalf("client sent command %v, want %v", cmd, "UID FETCH 4:* (FLAGS)")
487+
}
488+
489+
s.WriteString("* 23 FETCH (UID 2 FLAGS (\\Seen))\r\n")
490+
s.WriteString(tag + " OK FETCH completed\r\n")
491+
492+
if err := <-done; err != nil {
493+
t.Fatalf("c.Fetch() = %v", err)
494+
}
495+
496+
msg, ok := <-messages
497+
if !ok {
498+
t.Errorf("No message supplied")
499+
} else if msg.Uid != 2 {
500+
t.Errorf("First message has bad UID: %v", msg.Uid)
501+
}
502+
}
503+
381504
func TestClient_Store(t *testing.T) {
382505
c, s := newTestClient(t)
383506
defer s.Close()

responses/fetch.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ const fetchName = "FETCH"
1010
// See RFC 3501 section 7.4.2
1111
type Fetch struct {
1212
Messages chan *imap.Message
13+
SeqSet *imap.SeqSet
14+
Uid bool
1315
}
1416

1517
func (r *Fetch) Handle(resp imap.Resp) error {
@@ -31,6 +33,27 @@ func (r *Fetch) Handle(resp imap.Resp) error {
3133
return err
3234
}
3335

36+
if r.Uid && msg.Uid == 0 {
37+
// we requested UIDs and got a message without one --> unilateral update --> ignore
38+
return ErrUnhandled
39+
}
40+
41+
var num uint32
42+
if r.Uid {
43+
num = msg.Uid
44+
} else {
45+
num = seqNum
46+
}
47+
48+
// Check whether we obtained a result we requested with our SeqSet
49+
// If the result is not contained in our SeqSet we have to handle an additional special case:
50+
// In case we requested UIDs with a dynamic sequence (i.e. * or n:*) and the maximum UID of the mailbox
51+
// is less then our n, the server will supply us with the max UID (cf. RFC 3501 §6.4.8 and §9 `seq-range`).
52+
// Thus, such a result is correct and has to be returned by us.
53+
if !r.SeqSet.Contains(num) && (!r.Uid || !r.SeqSet.Dynamic()) {
54+
return ErrUnhandled
55+
}
56+
3457
r.Messages <- msg
3558
return nil
3659
}

0 commit comments

Comments
 (0)