fix: use canonical INBOX in Sieve filter paths #313

This commit is contained in:
Linus Rath
2026-05-22 11:51:25 +02:00
parent 66b2036e37
commit 1c02970ae1
2 changed files with 29 additions and 9 deletions
+4 -1
View File
@@ -78,7 +78,10 @@ export function FilterRuleModal({
const pathMap = new Map<string, string>();
const buildPaths = (nodes: MailboxNode[], parentPath = "") => {
for (const node of nodes) {
const fullPath = parentPath ? `${parentPath}/${node.name}` : node.name;
// Sieve fileinto expects the IMAP-canonical "INBOX" for the inbox,
// not the localized JMAP display name (e.g. "Entrada" in pt-BR).
const segment = node.role === "inbox" ? "INBOX" : node.name;
const fullPath = parentPath ? `${parentPath}/${segment}` : segment;
pathMap.set(node.id, fullPath);
if (node.children.length > 0) buildPaths(node.children, fullPath);
}
+25 -8
View File
@@ -33,7 +33,8 @@ function buildMailboxPathMap(tree: MailboxNode[]): Map<string, string> {
const pathMap = new Map<string, string>();
const walk = (nodes: MailboxNode[], parentPath = '') => {
for (const node of nodes) {
const fullPath = parentPath ? `${parentPath}/${node.name}` : node.name;
const segment = node.role === 'inbox' ? 'INBOX' : node.name;
const fullPath = parentPath ? `${parentPath}/${segment}` : segment;
pathMap.set(node.id, fullPath);
if (node.children.length > 0) walk(node.children, fullPath);
}
@@ -47,7 +48,7 @@ describe('mailbox path building for sieve fileinto', () => {
const mailboxes = [makeMailbox({ id: 'inbox', name: 'Inbox', role: 'inbox' })];
const tree = buildMailboxTree(mailboxes);
const paths = buildMailboxPathMap(tree);
expect(paths.get('inbox')).toBe('Inbox');
expect(paths.get('inbox')).toBe('INBOX');
});
it('should produce correct path for a single-level subfolder', () => {
@@ -57,7 +58,7 @@ describe('mailbox path building for sieve fileinto', () => {
];
const tree = buildMailboxTree(mailboxes);
const paths = buildMailboxPathMap(tree);
expect(paths.get('sub1')).toBe('Inbox/Projects');
expect(paths.get('sub1')).toBe('INBOX/Projects');
});
it('should produce correct path for deeply nested subfolders', () => {
@@ -68,7 +69,7 @@ describe('mailbox path building for sieve fileinto', () => {
];
const tree = buildMailboxTree(mailboxes);
const paths = buildMailboxPathMap(tree);
expect(paths.get('sub2')).toBe('Inbox/Test/Test2');
expect(paths.get('sub2')).toBe('INBOX/Test/Test2');
});
it('should handle multiple root-level folders', () => {
@@ -79,7 +80,7 @@ describe('mailbox path building for sieve fileinto', () => {
];
const tree = buildMailboxTree(mailboxes);
const paths = buildMailboxPathMap(tree);
expect(paths.get('inbox')).toBe('Inbox');
expect(paths.get('inbox')).toBe('INBOX');
expect(paths.get('archive')).toBe('Archive');
expect(paths.get('sub1')).toBe('Archive/Work');
});
@@ -99,9 +100,25 @@ describe('mailbox path building for sieve fileinto', () => {
expect(paths.has(node.id)).toBe(true);
}
expect(paths.get('inbox')).toBe('Inbox');
expect(paths.get('sub1')).toBe('Inbox/Projects');
expect(paths.get('sub2')).toBe('Inbox/Projects/Active');
expect(paths.get('inbox')).toBe('INBOX');
expect(paths.get('sub1')).toBe('INBOX/Projects');
expect(paths.get('sub2')).toBe('INBOX/Projects/Active');
});
it('uses canonical INBOX even when JMAP returns a localized inbox name', () => {
// Stalwart returns localized display names for the inbox based on the
// user's locale (e.g. "Entrada" for pt-BR). Sieve fileinto must still
// target the IMAP-canonical "INBOX" so the message is filed correctly.
const mailboxes = [
makeMailbox({ id: 'inbox', name: 'Entrada', role: 'inbox' }),
makeMailbox({ id: 'host', name: 'Host', parentId: 'inbox' }),
makeMailbox({ id: 'eveo', name: 'EVEO', parentId: 'host' }),
];
const tree = buildMailboxTree(mailboxes);
const paths = buildMailboxPathMap(tree);
expect(paths.get('inbox')).toBe('INBOX');
expect(paths.get('host')).toBe('INBOX/Host');
expect(paths.get('eveo')).toBe('INBOX/Host/EVEO');
});
it('should preserve depth info in flattened tree', () => {