Skip to content

Commit dd3b927

Browse files
authored
fix: ZMS-29: Various IMAP fixes (#1023)
* ZMS-29: IMAP fixes * fix tests
1 parent 6a836b8 commit dd3b927

9 files changed

Lines changed: 130 additions & 38 deletions

File tree

imap-core/lib/commands/fetch.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ module.exports = {
5757
let markAsSeen = false;
5858
let metadataOnly = true;
5959
let changedSince = 0;
60+
let changedSinceSpecified = false;
6061
let query = [];
6162

6263
let params = [].concat(command.attributes[1] || []);
@@ -67,7 +68,8 @@ module.exports = {
6768
return callback(new Error('Invalid modifier for ' + command.command));
6869
}
6970
changedSince = Number(extensions[1]);
70-
if (changedSince && !this.selected.condstoreEnabled) {
71+
changedSinceSpecified = true;
72+
if (!this.selected.condstoreEnabled) {
7173
this.condstoreEnabled = this.selected.condstoreEnabled = true;
7274
}
7375
}
@@ -117,6 +119,9 @@ module.exports = {
117119

118120
if (param.value.toUpperCase() === 'MODSEQ') {
119121
modseqExist = true;
122+
if (!this.selected.condstoreEnabled) {
123+
this.condstoreEnabled = this.selected.condstoreEnabled = true;
124+
}
120125
}
121126

122127
if (param.value.toUpperCase() === 'BODYSTRUCTURE') {
@@ -174,7 +179,7 @@ module.exports = {
174179
}
175180

176181
// ensure MODSEQ is listed if the command uses CHANGEDSINCE modifier
177-
if (changedSince && !modseqExist) {
182+
if (changedSinceSpecified && !modseqExist) {
178183
params.push({
179184
type: 'ATOM',
180185
value: 'MODSEQ'

imap-core/lib/commands/select.js

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -183,33 +183,31 @@ module.exports = {
183183
this.send('* 0 RECENT');
184184

185185
// * OK [HIGHESTMODSEQ 123]
186-
if ('modifyIndex' in mailboxData && Number(mailboxData.modifyIndex)) {
187-
this.send(
188-
imapHandler.compiler({
189-
tag: '*',
190-
command: 'OK',
191-
attributes: [
192-
{
193-
type: 'section',
194-
section: [
195-
{
196-
type: 'atom',
197-
value: 'HIGHESTMODSEQ'
198-
},
199-
{
200-
type: 'atom',
201-
value: String(Number(mailboxData.modifyIndex) || 0)
202-
}
203-
]
204-
},
205-
{
206-
type: 'text',
207-
value: 'Highest'
208-
}
209-
]
210-
})
211-
);
212-
}
186+
this.send(
187+
imapHandler.compiler({
188+
tag: '*',
189+
command: 'OK',
190+
attributes: [
191+
{
192+
type: 'section',
193+
section: [
194+
{
195+
type: 'atom',
196+
value: 'HIGHESTMODSEQ'
197+
},
198+
{
199+
type: 'atom',
200+
value: String(Number(mailboxData.modifyIndex) || 1)
201+
}
202+
]
203+
},
204+
{
205+
type: 'text',
206+
value: 'Highest'
207+
}
208+
]
209+
})
210+
);
213211

214212
// * OK [UIDNEXT 1] Predicted next UID
215213
this.send(

imap-core/lib/commands/store.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,8 @@ module.exports = {
183183
typeof success === 'string'
184184
? success.toUpperCase()
185185
: modified && modified.length
186-
? 'MODIFIED ' + imapTools.packMessageRange(modified)
187-
: false,
186+
? 'MODIFIED ' + imapTools.packMessageRange(modified)
187+
: false,
188188
message
189189
};
190190

@@ -198,6 +198,7 @@ module.exports = {
198198
});
199199
response = {
200200
response: 'NO',
201+
code: response.code,
201202
message: err.message
202203
};
203204
break;

imap-core/test/protocol-test.js

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,27 @@ describe('IMAP Protocol integration tests', function () {
476476
);
477477
});
478478

479+
it('should default HIGHESTMODSEQ to 1 for a new mailbox', function (done) {
480+
let cmds = ['T1 LOGIN testuser pass', 'T2 CREATE modseqtest', 'T3 SELECT modseqtest', 'T4 STATUS modseqtest (HIGHESTMODSEQ)', 'T5 LOGOUT'];
481+
482+
testClient(
483+
{
484+
commands: cmds,
485+
secure: true,
486+
port
487+
},
488+
function (resp) {
489+
resp = resp.toString();
490+
expect(/^T2 OK/m.test(resp)).to.be.true;
491+
expect(/^\* OK \[HIGHESTMODSEQ 1\]/m.test(resp)).to.be.true;
492+
expect(/^T3 OK \[READ-WRITE\]/m.test(resp)).to.be.true;
493+
expect(/^\* STATUS modseqtest \(HIGHESTMODSEQ 1\)$/m.test(resp)).to.be.true;
494+
expect(/^T4 OK/m.test(resp)).to.be.true;
495+
done();
496+
}
497+
);
498+
});
499+
479500
it(`cannot create a mailbox with subpath length bigger than ${MAX_MAILBOX_NAME_LENGTH} chars`, function (done) {
480501
let cmds = [
481502
'T1 LOGIN testuser pass',
@@ -1072,6 +1093,54 @@ describe('IMAP Protocol integration tests', function () {
10721093
}
10731094
);
10741095
});
1096+
1097+
it('should return MODIFIED when conditional STORE also targets expunged messages', function (done) {
1098+
let mailbox = 'condstore-race';
1099+
let message = Buffer.from('From: sender <[email protected]>\r\nTo: [email protected]\r\nSubject: HELLO!\r\n\r\nWORLD!');
1100+
1101+
let setupCmds = ['S1 LOGIN testuser pass', 'S2 CREATE ' + mailbox];
1102+
for (let i = 0; i < 7; i++) {
1103+
setupCmds.push('S' + (i + 3) + ' APPEND ' + mailbox + ' {' + message.length + '}\r\n' + message.toString('binary'));
1104+
}
1105+
setupCmds.push('S10 SELECT ' + mailbox);
1106+
setupCmds.push('S11 STORE 2 +FLAGS (MyFlag1)');
1107+
setupCmds.push('S12 LOGOUT');
1108+
1109+
let primaryCmds = ['A1 LOGIN testuser pass', 'A2 SELECT ' + mailbox, 'SLEEP', 'B001 STORE 1:7 (UNCHANGEDSINCE 7) +FLAGS (\\Seen)', 'A4 LOGOUT'];
1110+
let expungeCmds = ['C1 LOGIN testuser pass', 'C2 SELECT ' + mailbox, 'C3 STORE 4:7 +FLAGS.SILENT (\\Deleted)', 'C4 EXPUNGE', 'C5 LOGOUT'];
1111+
1112+
let runClient = commands =>
1113+
new Promise(resolve => {
1114+
testClient(
1115+
{
1116+
commands,
1117+
secure: true,
1118+
port
1119+
},
1120+
resp => resolve(resp.toString())
1121+
);
1122+
});
1123+
1124+
runClient(setupCmds)
1125+
.then(setupResp => {
1126+
expect(/^S11 OK/m.test(setupResp)).to.be.true;
1127+
1128+
let primaryRun = runClient(primaryCmds);
1129+
let expungeRun = new Promise(resolve => setTimeout(resolve, 1000)).then(() => runClient(expungeCmds));
1130+
1131+
return Promise.all([primaryRun, expungeRun]);
1132+
})
1133+
.then(([primaryResp, expungeResp]) => {
1134+
expect(/^C4 OK/m.test(expungeResp)).to.be.true;
1135+
expect(primaryResp.match(/^\* \d+ FETCH \(FLAGS \(\\Seen\) MODSEQ \(\d+\)\)$/gm)).to.have.length(2);
1136+
expect(/^\* 1 FETCH \(FLAGS \(\\Seen\) MODSEQ \(\d+\)\)$/m.test(primaryResp)).to.be.true;
1137+
expect(/^\* 3 FETCH \(FLAGS \(\\Seen\) MODSEQ \(\d+\)\)$/m.test(primaryResp)).to.be.true;
1138+
expect(/^\* \d+ EXPUNGE$/m.test(primaryResp)).to.be.false;
1139+
expect(/^B001 NO \[MODIFIED 2\] Some of the messages no longer exist$/m.test(primaryResp)).to.be.true;
1140+
done();
1141+
})
1142+
.catch(done);
1143+
});
10751144
});
10761145

10771146
describe('UID STORE', function () {
@@ -1312,8 +1381,8 @@ describe('IMAP Protocol integration tests', function () {
13121381
},
13131382
function (resp) {
13141383
resp = resp.toString();
1315-
expect(resp.slice(/\n/).indexOf('* 3 FETCH (FLAGS (\\Seen) UID 103 MODSEQ (3))') >= 0).to.be.true; // UID FETCH FLAGS
1316-
expect(resp.slice(/\n/).indexOf('* 3 FETCH (FLAGS (\\Seen) MODSEQ (3))') >= 0).to.be.true; // FETCH FLAGS
1384+
expect(/^\* 3 FETCH \(FLAGS \(\\Seen\) UID 103 MODSEQ \(4\)\)$/m.test(resp)).to.be.true; // UID FETCH FLAGS
1385+
expect(/^\* 3 FETCH \(FLAGS \(\\Seen\) MODSEQ \(4\)\)$/m.test(resp)).to.be.true; // FETCH FLAGS
13171386
expect(resp.match(/^\* \d+ FETCH/gm).length).to.equal(2);
13181387
expect(/^T3 OK/m.test(resp)).to.be.true;
13191388
expect(/^T4 OK/m.test(resp)).to.be.true;
@@ -1324,6 +1393,25 @@ describe('IMAP Protocol integration tests', function () {
13241393
);
13251394
});
13261395

1396+
it('should enable CONDSTORE after FETCH MODSEQ', function (done) {
1397+
let cmds = ['T1 LOGIN testuser pass', 'T2 SELECT INBOX', 'T3 FETCH 1 (MODSEQ)', 'T4 STORE 1 +FLAGS (MyFlag1)', 'T5 LOGOUT'];
1398+
1399+
testClient(
1400+
{
1401+
commands: cmds,
1402+
secure: true,
1403+
port
1404+
},
1405+
function (resp) {
1406+
resp = resp.toString();
1407+
expect(/^\* 1 FETCH \(MODSEQ \(\d+\)\)$/m.test(resp)).to.be.true;
1408+
expect(/^\* 1 FETCH \(FLAGS \(MyFlag1\) MODSEQ \(\d+\)\)$/m.test(resp)).to.be.true;
1409+
expect(/^T4 OK/m.test(resp)).to.be.true;
1410+
done();
1411+
}
1412+
);
1413+
});
1414+
13271415
describe('Multiple values', function () {
13281416
it('should list mixed data', function (done) {
13291417
let cmds = ['T1 LOGIN testuser pass', 'T2 SELECT INBOX', 'T3 FETCH 1:* (UID BODYSTRUCTURE ENVELOPE)', 'T4 LOGOUT'];

imap-core/test/test-server.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ module.exports = function (options) {
202202
path: mailbox,
203203
uidValidity: Date.now(),
204204
uidNext: 1,
205-
modifyIndex: 0,
205+
modifyIndex: 1,
206206
messages: [],
207207
journal: []
208208
});

lib/api/users.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2084,7 +2084,7 @@ module.exports = (db, server, userHandler, settingsHandler) => {
20842084
case 'mailboxes':
20852085
document.uidValidity = Math.floor(Date.now() / 1000);
20862086
document.uidNext = 1;
2087-
document.modifyIndex = 0;
2087+
document.modifyIndex = 1;
20882088
break;
20892089
}
20902090

lib/handlers/on-status.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ module.exports = server => (path, session, callback) => {
5959
uidNext: mailboxData.uidNext,
6060
uidValidity: mailboxData.uidValidity,
6161
unseen,
62-
highestModseq: Number(mailboxData.modifyIndex) || 0
62+
highestModseq: Number(mailboxData.modifyIndex) || 1
6363
});
6464
}
6565
);

lib/mailbox-handler.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class MailboxHandler {
8585
path,
8686
uidValidity: Math.floor(Date.now() / 1000),
8787
uidNext: 1,
88-
modifyIndex: 0,
88+
modifyIndex: 1,
8989
subscribed: true,
9090
flags: [],
9191
retention: userData.retention

lib/user-handler.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3476,7 +3476,7 @@ class UserHandler {
34763476
specialUse: mailbox.specialUse,
34773477
uidValidity,
34783478
uidNext: 1,
3479-
modifyIndex: 0,
3479+
modifyIndex: 1,
34803480
subscribed: true,
34813481
flags: []
34823482
}));

0 commit comments

Comments
 (0)