Skip to content

Commit 47e2fb7

Browse files
cuthixLBeernaertProton
authored andcommitted
fix(GODT-1616): close not sending unilateral updates.
1 parent 5bf5ac4 commit 47e2fb7

4 files changed

Lines changed: 67 additions & 40 deletions

File tree

internal/state/responders.go

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"sync"
77

88
"github.com/ProtonMail/gluon/imap"
9+
"github.com/ProtonMail/gluon/internal/contexts"
910
"github.com/ProtonMail/gluon/internal/db"
1011
"github.com/ProtonMail/gluon/internal/db/ent"
1112
"github.com/ProtonMail/gluon/internal/ids"
@@ -52,7 +53,7 @@ func NewMessageIDAndMailboxIDResponderStateUpdate(messageID imap.InternalMessage
5253

5354
type Responder interface {
5455
// handle generates responses in the context of the given snapshot.
55-
handle(snap *snapshot, stateID StateID) ([]response.Response, responderDBUpdate, error)
56+
handle(ctx context.Context, snap *snapshot, stateID StateID) ([]response.Response, responderDBUpdate, error)
5657

5758
// getMessageID returns the message ID that this Responder targets.
5859
getMessageID() imap.InternalMessageID
@@ -90,7 +91,7 @@ type targetedExists struct {
9091
targetStateID StateID
9192
}
9293

93-
func (u *targetedExists) handle(snap *snapshot, stateID StateID) ([]response.Response, responderDBUpdate, error) {
94+
func (u *targetedExists) handle(_ context.Context, snap *snapshot, stateID StateID) ([]response.Response, responderDBUpdate, error) {
9495
if snap.hasMessage(u.resp.messageID.InternalID) {
9596
return nil, nil, nil
9697
}
@@ -229,17 +230,15 @@ func (e *ExistsStateUpdate) String() string {
229230

230231
type expunge struct {
231232
messageID imap.InternalMessageID
232-
asClose bool
233233
}
234234

235-
func NewExpunge(messageID imap.InternalMessageID, asClose bool) *expunge {
235+
func NewExpunge(messageID imap.InternalMessageID) *expunge {
236236
return &expunge{
237237
messageID: messageID,
238-
asClose: asClose,
239238
}
240239
}
241240

242-
func (u *expunge) handle(snap *snapshot, _ StateID) ([]response.Response, responderDBUpdate, error) {
241+
func (u *expunge) handle(ctx context.Context, snap *snapshot, _ StateID) ([]response.Response, responderDBUpdate, error) {
243242
if !snap.hasMessage(u.messageID) {
244243
return nil, nil, nil
245244
}
@@ -254,7 +253,7 @@ func (u *expunge) handle(snap *snapshot, _ StateID) ([]response.Response, respon
254253
}
255254

256255
// When handling a CLOSE command, EXPUNGE responses are not sent.
257-
if u.asClose {
256+
if contexts.IsClose(ctx) {
258257
return nil, nil, nil
259258
}
260259

@@ -266,9 +265,8 @@ func (u *expunge) getMessageID() imap.InternalMessageID {
266265
}
267266

268267
func (u *expunge) String() string {
269-
return fmt.Sprintf("Expung: message = %v closed = %v",
268+
return fmt.Sprintf("Expung: message = %v",
270269
u.messageID.ShortID(),
271-
u.asClose,
272270
)
273271
}
274272

@@ -299,7 +297,7 @@ func NewFetch(messageID imap.InternalMessageID, flags imap.FlagSet, asUID, asSil
299297
}
300298
}
301299

302-
func (u *fetch) handle(snap *snapshot, _ StateID) ([]response.Response, responderDBUpdate, error) {
300+
func (u *fetch) handle(_ context.Context, snap *snapshot, _ StateID) ([]response.Response, responderDBUpdate, error) {
303301
if !snap.hasMessage(u.messageID) {
304302
return nil, nil, nil
305303
}

internal/state/state.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@ func (state *State) flushResponses(ctx context.Context, permitExpunge bool) ([]r
532532
for _, responder := range state.popResponders(permitExpunge) {
533533
logrus.WithField("state", state.StateID).WithField("Origin", "Flush").Tracef("Applying responder: %v", responder.String())
534534

535-
res, dbUpdate, err := responder.handle(state.snap, state.StateID)
535+
res, dbUpdate, err := responder.handle(ctx, state.snap, state.StateID)
536536
if err != nil {
537537
return nil, err
538538
}
@@ -567,7 +567,7 @@ func (state *State) PushResponder(ctx context.Context, tx *ent.Tx, responder ...
567567
for _, responder := range responder {
568568
logrus.WithField("state", state.StateID).WithField("Origin", "Push").Tracef("Applying responder: %v", responder.String())
569569

570-
res, dbUpdate, err := responder.handle(state.snap, state.StateID)
570+
res, dbUpdate, err := responder.handle(ctx, state.snap, state.StateID)
571571
if err != nil {
572572
return err
573573
}

internal/state/updates_mailbox.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55

66
"github.com/ProtonMail/gluon/imap"
7-
"github.com/ProtonMail/gluon/internal/contexts"
87
"github.com/ProtonMail/gluon/internal/db"
98
"github.com/ProtonMail/gluon/internal/db/ent"
109
"github.com/ProtonMail/gluon/internal/ids"
@@ -46,7 +45,7 @@ func MoveMessagesFromMailbox(
4645
}
4746

4847
for _, messageID := range messageIDs {
49-
stateUpdates = append(stateUpdates, NewMessageIDAndMailboxIDResponderStateUpdate(messageID, mboxFromID, NewExpunge(messageID, contexts.IsClose(ctx))))
48+
stateUpdates = append(stateUpdates, NewMessageIDAndMailboxIDResponderStateUpdate(messageID, mboxFromID, NewExpunge(messageID)))
5049
}
5150

5251
return messageUIDs, stateUpdates, nil
@@ -80,7 +79,7 @@ func RemoveMessagesFromMailbox(ctx context.Context, tx *ent.Tx, mboxID imap.Inte
8079
}
8180

8281
stateUpdates := xslices.Map(messageIDs, func(id imap.InternalMessageID) Update {
83-
return NewMessageIDAndMailboxIDResponderStateUpdate(id, mboxID, NewExpunge(id, contexts.IsClose(ctx)))
82+
return NewMessageIDAndMailboxIDResponderStateUpdate(id, mboxID, NewExpunge(id))
8483
})
8584

8685
return stateUpdates, nil

tests/close_test.go

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,40 +14,70 @@ func TestClose(t *testing.T) {
1414
// This test is still useful as we have no way of checking the fetch responses after the store command.
1515
// Additionally, we also need to ensure that there are no unilateral EXPUNGE messages returned from the server after close.
1616
// There is currently no way to check for this with the go imap client.
17-
runOneToOneTestWithAuth(t, defaultServerOptions(t), func(c *testConnection, _ *testSession) {
18-
c.C("b001 CREATE saved-messages")
19-
c.S("b001 OK CREATE")
17+
runManyToOneTestWithAuth(t, defaultServerOptions(t), []int{1, 2}, func(c map[int]*testConnection, _ *testSession) {
18+
c[1].C("b001 CREATE saved-messages")
19+
c[1].S("b001 OK CREATE")
2020

21-
c.doAppend(`saved-messages`, `To: 1@pm.me`, `\Seen`).expect("OK")
22-
c.doAppend(`saved-messages`, `To: 2@pm.me`).expect("OK")
23-
c.doAppend(`saved-messages`, `To: 3@pm.me`, `\Seen`).expect("OK")
24-
c.doAppend(`saved-messages`, `To: 4@pm.me`).expect("OK")
25-
c.doAppend(`saved-messages`, `To: 5@pm.me`, `\Seen`).expect("OK")
21+
c[1].doAppend(`saved-messages`, `To: 1@pm.me`, `\Seen`).expect("OK")
22+
c[1].doAppend(`saved-messages`, `To: 2@pm.me`).expect("OK")
23+
c[1].doAppend(`saved-messages`, `To: 3@pm.me`, `\Seen`).expect("OK")
24+
c[1].doAppend(`saved-messages`, `To: 4@pm.me`).expect("OK")
25+
c[1].doAppend(`saved-messages`, `To: 5@pm.me`, `\Seen`).expect("OK")
2626

27-
c.C(`A002 SELECT saved-messages`)
28-
c.Se(`A002 OK [READ-WRITE] SELECT`)
27+
c[1].C(`A002 SELECT saved-messages`)
28+
c[1].Se(`A002 OK [READ-WRITE] SELECT`)
29+
30+
c[2].C(`B001 SELECT saved-messages`)
31+
c[2].Se(`B001 OK [READ-WRITE] SELECT`)
32+
c[2].C(`B002 FETCH 1:* (UID FLAGS)`)
33+
c[2].S(
34+
`* 1 FETCH (UID 1 FLAGS (\Seen))`,
35+
`* 2 FETCH (UID 2 FLAGS ())`,
36+
`* 3 FETCH (UID 3 FLAGS (\Seen))`,
37+
`* 4 FETCH (UID 4 FLAGS ())`,
38+
`* 5 FETCH (UID 5 FLAGS (\Seen))`,
39+
)
40+
c[2].OK("B002")
2941

3042
// TODO: Match flags in any order.
31-
c.C(`A003 STORE 1 +FLAGS (\Deleted)`)
32-
c.S(`* 1 FETCH (FLAGS (\Deleted \Recent \Seen))`)
33-
c.Sx("A003 OK.*")
43+
c[1].C(`A003 STORE 1 +FLAGS (\Deleted)`)
44+
c[1].S(`* 1 FETCH (FLAGS (\Deleted \Recent \Seen))`)
45+
c[1].Sx("A003 OK.*")
3446

35-
c.C(`A004 STORE 2 +FLAGS (\Deleted)`)
36-
c.S(`* 2 FETCH (FLAGS (\Deleted \Recent))`)
37-
c.Sx("A004 OK.*")
47+
c[1].C(`A004 STORE 2 +FLAGS (\Deleted)`)
48+
c[1].S(`* 2 FETCH (FLAGS (\Deleted \Recent))`)
49+
c[1].Sx("A004 OK.*")
3850

39-
c.C(`A005 STORE 3 +FLAGS (\Deleted)`)
40-
c.S(`* 3 FETCH (FLAGS (\Deleted \Recent \Seen))`)
41-
c.Sx("A005 OK.*")
51+
c[1].C(`A005 STORE 3 +FLAGS (\Deleted)`)
52+
c[1].S(`* 3 FETCH (FLAGS (\Deleted \Recent \Seen))`)
53+
c[1].Sx("A005 OK.*")
54+
55+
c[2].C("B003 NOOP")
56+
c[2].S(
57+
`* 1 FETCH (FLAGS (\Deleted \Seen))`,
58+
`* 2 FETCH (FLAGS (\Deleted))`,
59+
`* 3 FETCH (FLAGS (\Deleted \Seen))`,
60+
)
61+
c[2].OK("B003")
4262

43-
// TODO: GOMSRV-106 - Ensure this also works for cases where multiple clients have the same mailbox open
44-
c.C(`A202 CLOSE`)
45-
c.S("A202 OK CLOSE")
63+
c[2].C("B004 UID EXPUNGE 1")
64+
c[2].Se(`* 1 EXPUNGE`)
65+
c[2].OK("B004")
66+
67+
c[1].C(`A202 CLOSE`)
68+
c[1].S("A202 OK CLOSE")
69+
70+
c[2].C("B003 NOOP")
71+
c[2].S(
72+
`* 1 EXPUNGE`,
73+
`* 1 EXPUNGE`,
74+
)
75+
c[2].OK("B003")
4676

4777
// There are 2 messages in saved-messages.
48-
c.C(`A006 STATUS saved-messages (MESSAGES)`)
49-
c.S(`* STATUS "saved-messages" (MESSAGES 2)`)
50-
c.S(`A006 OK STATUS`)
78+
c[1].C(`A006 STATUS saved-messages (MESSAGES)`)
79+
c[1].S(`* STATUS "saved-messages" (MESSAGES 2)`)
80+
c[1].S(`A006 OK STATUS`)
5181
})
5282
}
5383

0 commit comments

Comments
 (0)