imapflow 1.4.6 → 1.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.4.7](https://github.com/postalsys/imapflow/compare/v1.4.6...v1.4.7) (2026-07-10)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* treat \NonExistent mailboxes as \Noselect in LIST responses ([af21245](https://github.com/postalsys/imapflow/commit/af21245dab4e1320afc4c4458afe33d1508180c1))
|
|
9
|
+
|
|
3
10
|
## [1.4.6](https://github.com/postalsys/imapflow/compare/v1.4.5...v1.4.6) (2026-07-05)
|
|
4
11
|
|
|
5
12
|
|
package/lib/commands/list.js
CHANGED
|
@@ -87,6 +87,15 @@ module.exports = async (connection, reference, mailbox, options) => {
|
|
|
87
87
|
specialUseMatches[type].push({ entry, source });
|
|
88
88
|
};
|
|
89
89
|
|
|
90
|
+
// RFC 5258: the \NonExistent attribute implies \Noselect. Some servers only
|
|
91
|
+
// return \NonExistent for phantom folders, so add \Noselect as well to keep
|
|
92
|
+
// the flags consistent for consumers that only check \Noselect.
|
|
93
|
+
let normalizeFlags = entry => {
|
|
94
|
+
if (entry.flags.has('\\NonExistent')) {
|
|
95
|
+
entry.flags.add('\\Noselect');
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
90
99
|
// User-provided hints map mailbox paths to special-use types (e.g., {sent: "Sent Items"}).
|
|
91
100
|
// These override server-reported flags and name-based guesses. Converted to a
|
|
92
101
|
// path-keyed lookup: { "Sent Items" => "\\Sent" }
|
|
@@ -132,6 +141,8 @@ module.exports = async (connection, reference, mailbox, options) => {
|
|
|
132
141
|
listed: true
|
|
133
142
|
};
|
|
134
143
|
|
|
144
|
+
normalizeFlags(entry);
|
|
145
|
+
|
|
135
146
|
// Check user-provided hints first (highest priority)
|
|
136
147
|
if (specialUseHints[entry.path]) {
|
|
137
148
|
addSpecialUseMatch(entry, specialUseHints[entry.path], 'user');
|
|
@@ -300,6 +311,7 @@ module.exports = async (connection, reference, mailbox, options) => {
|
|
|
300
311
|
existing.subscribed = true;
|
|
301
312
|
// Merge any additional flags from LSUB into the LIST entry
|
|
302
313
|
entry.flags.forEach(flag => existing.flags.add(flag));
|
|
314
|
+
normalizeFlags(existing);
|
|
303
315
|
}
|
|
304
316
|
// Non-listed subscribed folders are intentionally ignored
|
|
305
317
|
}
|
package/package.json
CHANGED
|
@@ -3550,6 +3550,94 @@ module.exports['Commands: list skips Noselect folders for status'] = async test
|
|
|
3550
3550
|
test.done();
|
|
3551
3551
|
};
|
|
3552
3552
|
|
|
3553
|
+
module.exports['Commands: list adds Noselect to NonExistent mailboxes'] = async test => {
|
|
3554
|
+
const connection = createMockConnection({
|
|
3555
|
+
state: 3,
|
|
3556
|
+
exec: async (cmd, attrs, opts) => {
|
|
3557
|
+
if (cmd === 'LIST' && opts && opts.untagged && opts.untagged.LIST) {
|
|
3558
|
+
await opts.untagged.LIST({
|
|
3559
|
+
attributes: [[{ value: '\\HasNoChildren' }], { value: '/' }, { value: 'INBOX' }]
|
|
3560
|
+
});
|
|
3561
|
+
await opts.untagged.LIST({
|
|
3562
|
+
attributes: [[{ value: '\\NonExistent' }], { value: '/' }, { value: 'Phantom' }]
|
|
3563
|
+
});
|
|
3564
|
+
}
|
|
3565
|
+
return { next: () => {} };
|
|
3566
|
+
}
|
|
3567
|
+
});
|
|
3568
|
+
|
|
3569
|
+
const result = await listCommand(connection, '', '*');
|
|
3570
|
+
const phantom = result.find(e => e.path === 'Phantom');
|
|
3571
|
+
test.ok(phantom);
|
|
3572
|
+
// RFC 5258: \\NonExistent implies \\Noselect
|
|
3573
|
+
test.equal(phantom.flags.has('\\Noselect'), true);
|
|
3574
|
+
// The original flag is preserved, not replaced
|
|
3575
|
+
test.equal(phantom.flags.has('\\NonExistent'), true);
|
|
3576
|
+
const inbox = result.find(e => e.path === 'INBOX');
|
|
3577
|
+
test.ok(inbox);
|
|
3578
|
+
test.equal(inbox.flags.has('\\Noselect'), false);
|
|
3579
|
+
test.done();
|
|
3580
|
+
};
|
|
3581
|
+
|
|
3582
|
+
module.exports['Commands: list LSUB merge adds Noselect to NonExistent'] = async test => {
|
|
3583
|
+
const connection = createMockConnection({
|
|
3584
|
+
state: 3,
|
|
3585
|
+
exec: async (cmd, attrs, opts) => {
|
|
3586
|
+
if (cmd === 'LIST' && opts && opts.untagged && opts.untagged.LIST) {
|
|
3587
|
+
await opts.untagged.LIST({
|
|
3588
|
+
attributes: [[{ value: '\\HasNoChildren' }], { value: '/' }, { value: 'Folder1' }]
|
|
3589
|
+
});
|
|
3590
|
+
}
|
|
3591
|
+
if (cmd === 'LSUB' && opts && opts.untagged && opts.untagged.LSUB) {
|
|
3592
|
+
// Some servers only report \\NonExistent in LSUB responses
|
|
3593
|
+
await opts.untagged.LSUB({
|
|
3594
|
+
attributes: [[{ value: '\\NonExistent' }], { value: '/' }, { value: 'Folder1' }]
|
|
3595
|
+
});
|
|
3596
|
+
}
|
|
3597
|
+
return { next: () => {} };
|
|
3598
|
+
}
|
|
3599
|
+
});
|
|
3600
|
+
|
|
3601
|
+
const result = await listCommand(connection, '', '*');
|
|
3602
|
+
const folder = result.find(e => e.path === 'Folder1');
|
|
3603
|
+
test.ok(folder);
|
|
3604
|
+
test.equal(folder.subscribed, true);
|
|
3605
|
+
test.equal(folder.flags.has('\\NonExistent'), true);
|
|
3606
|
+
// RFC 5258: \\NonExistent merged from LSUB implies \\Noselect
|
|
3607
|
+
test.equal(folder.flags.has('\\Noselect'), true);
|
|
3608
|
+
test.done();
|
|
3609
|
+
};
|
|
3610
|
+
|
|
3611
|
+
module.exports['Commands: list does not STATUS NonExistent mailboxes'] = async test => {
|
|
3612
|
+
let statusPaths = [];
|
|
3613
|
+
const connection = createMockConnection({
|
|
3614
|
+
state: 3,
|
|
3615
|
+
capabilities: new Map(),
|
|
3616
|
+
run: async (cmd, path) => {
|
|
3617
|
+
if (cmd === 'STATUS') {
|
|
3618
|
+
statusPaths.push(path);
|
|
3619
|
+
return { messages: 10 };
|
|
3620
|
+
}
|
|
3621
|
+
},
|
|
3622
|
+
exec: async (cmd, attrs, opts) => {
|
|
3623
|
+
if (cmd === 'LIST' && opts && opts.untagged && opts.untagged.LIST) {
|
|
3624
|
+
await opts.untagged.LIST({
|
|
3625
|
+
attributes: [[{ value: '\\HasNoChildren' }], { value: '/' }, { value: 'INBOX' }]
|
|
3626
|
+
});
|
|
3627
|
+
await opts.untagged.LIST({
|
|
3628
|
+
attributes: [[{ value: '\\NonExistent' }], { value: '/' }, { value: 'Phantom' }]
|
|
3629
|
+
});
|
|
3630
|
+
}
|
|
3631
|
+
return { next: () => {} };
|
|
3632
|
+
}
|
|
3633
|
+
});
|
|
3634
|
+
|
|
3635
|
+
await listCommand(connection, '', '*', { statusQuery: { messages: true } });
|
|
3636
|
+
// STATUS runs for the selectable mailbox only
|
|
3637
|
+
test.deepEqual(statusPaths, ['INBOX']);
|
|
3638
|
+
test.done();
|
|
3639
|
+
};
|
|
3640
|
+
|
|
3553
3641
|
module.exports['Commands: list XLIST removes Inbox flag from non-INBOX'] = async test => {
|
|
3554
3642
|
const connection = createMockConnection({
|
|
3555
3643
|
state: 3,
|