From 3893849d21657236934c1d89ffb9fcbbefd6e13e Mon Sep 17 00:00:00 2001 From: djmaze <> Date: Tue, 24 Sep 2024 11:44:40 +0200 Subject: [PATCH] Fix Sieve math-type and comparator handling --- dev/Sieve/Commands/Tests.js | 16 +++++---- dev/Sieve/Extensions/rfc5173.js | 5 +-- dev/Sieve/Extensions/rfc5183.js | 5 +-- dev/Sieve/Extensions/rfc5229.js | 5 +-- dev/Sieve/Extensions/rfc5232.js | 5 +-- dev/Sieve/Extensions/rfc5235.js | 14 ++++---- dev/Sieve/Extensions/rfc5260.js | 10 +++--- dev/Sieve/Extensions/rfc5435.js | 5 +-- dev/Sieve/Extensions/rfc5490.js | 10 +++--- dev/Sieve/Grammar.js | 63 ++++++++++++++++++++++++++++----- dev/Sieve/Parser.js | 16 +++++---- dev/Sieve/Utils.js | 22 +++++++++++- 12 files changed, 128 insertions(+), 48 deletions(-) diff --git a/dev/Sieve/Commands/Tests.js b/dev/Sieve/Commands/Tests.js index f566fadae..093b746a2 100644 --- a/dev/Sieve/Commands/Tests.js +++ b/dev/Sieve/Commands/Tests.js @@ -68,9 +68,10 @@ export class AddressTest extends TestCommand } return result + (this.last ? ' :last' : (this.index.value ? ' :index ' + this.index : '')) - + (this.comparator ? ' :comparator ' + this.comparator : '') + + (this._comparator ? ' :comparator ' + this._comparator : '') + ' ' + this.address_part - + ' ' + this.match_type + + (this._match_type ? ' ' + this._match_type : '') + + (this.relational_match ? ' ' + this.relational_match : '') + ' ' + this.header_list + ' ' + this.key_list; } @@ -147,9 +148,10 @@ export class EnvelopeTest extends TestCommand toString() { return 'envelope' - + (this.comparator ? ' :comparator ' + this.comparator : '') + + (this._comparator ? ' :comparator ' + this._comparator : '') + ' ' + this.address_part - + ' ' + this.match_type + + (this._match_type ? ' ' + this._match_type : '') + + (this.relational_match ? ' ' + this.relational_match : '') + ' ' + this.envelope_part + ' ' + this.key_list; } @@ -230,7 +232,6 @@ export class HeaderTest extends TestCommand constructor() { super(); - this.match_type = ':is'; this.address_part = ':all'; this.header_names = new GrammarStringList; this.key_list = new GrammarStringList; @@ -280,8 +281,9 @@ export class HeaderTest extends TestCommand } return result + (this.last ? ' :last' : (this.index.value ? ' :index ' + this.index : '')) - + (this.comparator ? ' :comparator ' + this.comparator : '') - + ' ' + this.match_type + + (this._comparator ? ' :comparator ' + this._comparator : '') + + (this._match_type ? ' ' + this._match_type : '') + + (this.relational_match ? ' ' + this.relational_match : '') + ' ' + this.header_names + ' ' + this.key_list; } diff --git a/dev/Sieve/Extensions/rfc5173.js b/dev/Sieve/Extensions/rfc5173.js index d538735a4..d72b97f89 100644 --- a/dev/Sieve/Extensions/rfc5173.js +++ b/dev/Sieve/Extensions/rfc5173.js @@ -22,8 +22,9 @@ export class BodyTest extends TestCommand toString() { return 'body' - + (this.comparator ? ' :comparator ' + this.comparator : '') - + ' ' + this.match_type + + (this._comparator ? ' :comparator ' + this._comparator : '') + + (this._match_type ? ' ' + this._match_type : '') + + (this.relational_match ? ' ' + this.relational_match : '') + ' ' + this.body_transform + ' ' + this.key_list; } diff --git a/dev/Sieve/Extensions/rfc5183.js b/dev/Sieve/Extensions/rfc5183.js index a23e833df..65e51961b 100644 --- a/dev/Sieve/Extensions/rfc5183.js +++ b/dev/Sieve/Extensions/rfc5183.js @@ -25,8 +25,9 @@ export class EnvironmentTest extends TestCommand toString() { return 'environment' - + (this.comparator ? ' :comparator ' + this.comparator : '') - + ' ' + this.match_type + + (this._comparator ? ' :comparator ' + this._comparator : '') + + (this._match_type ? ' ' + this._match_type : '') + + (this.relational_match ? ' ' + this.relational_match : '') + ' ' + this._name + ' ' + this.key_list; } diff --git a/dev/Sieve/Extensions/rfc5229.js b/dev/Sieve/Extensions/rfc5229.js index 7bd3c3aeb..794260185 100644 --- a/dev/Sieve/Extensions/rfc5229.js +++ b/dev/Sieve/Extensions/rfc5229.js @@ -57,8 +57,9 @@ export class StringTest extends TestCommand toString() { return 'string' - + ' ' + this.match_type - + (this.comparator ? ' :comparator ' + this.comparator : '') + + (this._match_type ? ' ' + this._match_type : '') + + (this.relational_match ? ' ' + this.relational_match : '') + + (this._comparator ? ' :comparator ' + this._comparator : '') + ' ' + this.source + ' ' + this.key_list; } diff --git a/dev/Sieve/Extensions/rfc5232.js b/dev/Sieve/Extensions/rfc5232.js index 0577cde1d..ea56261f5 100644 --- a/dev/Sieve/Extensions/rfc5232.js +++ b/dev/Sieve/Extensions/rfc5232.js @@ -79,8 +79,9 @@ export class HasFlagTest extends TestCommand toString() { return 'hasflag' - + ' ' + this.match_type - + (this.comparator ? ' :comparator ' + this.comparator : '') + + (this._match_type ? ' ' + this._match_type : '') + + (this.relational_match ? ' ' + this.relational_match : '') + + (this._comparator ? ' :comparator ' + this._comparator : '') + ' ' + this.variable_list + ' ' + this.list_of_flags; } diff --git a/dev/Sieve/Extensions/rfc5235.js b/dev/Sieve/Extensions/rfc5235.js index 1c8bc46b2..ac1e8406e 100644 --- a/dev/Sieve/Extensions/rfc5235.js +++ b/dev/Sieve/Extensions/rfc5235.js @@ -17,7 +17,7 @@ export class SpamTestTest extends TestCommand } // get require() { return this.percent ? 'spamtestplus' : 'spamtest'; } - get require() { return /:value|:count/.test(this.match_type) ? ['spamtestplus','relational'] : 'spamtestplus'; } + get require() { return /:value|:count/.test(this._match_type) ? ['spamtestplus','relational'] : 'spamtestplus'; } get value() { return this._value.value; } set value(v) { this._value.value = v; } @@ -26,8 +26,9 @@ export class SpamTestTest extends TestCommand { return 'spamtest' + (this.percent ? ' :percent' : '') - + (this.comparator ? ' :comparator ' + this.comparator : '') - + ' ' + this.match_type + + (this._comparator ? ' :comparator ' + this._comparator : '') + + (this._match_type ? ' ' + this._match_type : '') + + (this.relational_match ? ' ' + this.relational_match : '') + ' ' + this._value; } @@ -51,7 +52,7 @@ export class VirusTestTest extends TestCommand this._value = new GrammarQuotedString; // 1 - 5 } - get require() { return /:value/.test(this.match_type) ? ['virustest','relational'] : 'virustest'; } + get require() { return ':value' == this._match_type ? ['virustest','relational'] : 'virustest'; } get value() { return this._value.value; } set value(v) { this._value.value = v; } @@ -59,8 +60,9 @@ export class VirusTestTest extends TestCommand toString() { return 'virustest' - + (this.comparator ? ' :comparator ' + this.comparator : '') - + ' ' + this.match_type + + (this._comparator ? ' :comparator ' + this._comparator : '') + + (this._match_type ? ' ' + this._match_type : '') + + (this.relational_match ? ' ' + this.relational_match : '') + ' ' + this._value; } diff --git a/dev/Sieve/Extensions/rfc5260.js b/dev/Sieve/Extensions/rfc5260.js index d888b72ae..2745a2087 100644 --- a/dev/Sieve/Extensions/rfc5260.js +++ b/dev/Sieve/Extensions/rfc5260.js @@ -41,8 +41,9 @@ export class DateTest extends TestCommand return 'date' + (this.last ? ' :last' : (this.index.value ? ' :index ' + this.index : '')) + (this.originalzone ? ' :originalzone' : (this._zone.length ? ' :zone ' + this._zone : '')) - + (this.comparator ? ' :comparator ' + this.comparator : '') - + ' ' + this.match_type + + (this._comparator ? ' :comparator ' + this._comparator : '') + + (this._match_type ? ' ' + this._match_type : '') + + (this.relational_match ? ' ' + this.relational_match : '') + ' ' + this._header_name + ' ' + this._date_part + ' ' + this.key_list; @@ -89,8 +90,9 @@ export class CurrentDateTest extends TestCommand { return 'currentdate' + (this._zone.length ? ' :zone ' + this._zone : '') - + (this.comparator ? ' :comparator ' + this.comparator : '') - + ' ' + this.match_type + + (this._comparator ? ' :comparator ' + this._comparator : '') + + (this._match_type ? ' ' + this._match_type : '') + + (this.relational_match ? ' ' + this.relational_match : '') + ' ' + this._date_part + ' ' + this.key_list; } diff --git a/dev/Sieve/Extensions/rfc5435.js b/dev/Sieve/Extensions/rfc5435.js index 9bee9fdb6..b6f040096 100644 --- a/dev/Sieve/Extensions/rfc5435.js +++ b/dev/Sieve/Extensions/rfc5435.js @@ -116,8 +116,9 @@ export class NotifyMethodCapabilityTest extends TestCommand toString() { return 'valid_notify_method ' - + (this.comparator ? ' :comparator ' + this.comparator : '') - + (this.match_type ? ' ' + this.match_type : '') + + (this._comparator ? ' :comparator ' + this._comparator : '') + + (this._match_type ? ' ' + this._match_type : '') + + (this.relational_match ? ' ' + this.relational_match : '') + this._notification_uri + this._notification_capability + this.key_list; diff --git a/dev/Sieve/Extensions/rfc5490.js b/dev/Sieve/Extensions/rfc5490.js index 036600763..94f7605b7 100644 --- a/dev/Sieve/Extensions/rfc5490.js +++ b/dev/Sieve/Extensions/rfc5490.js @@ -58,8 +58,9 @@ export class MetadataTest extends TestCommand toString() { return 'metadata ' - + ' ' + this.match_type - + (this.comparator ? ' :comparator ' + this.comparator : '') + + ' ' + this._match_type + + (this.relational_match ? ' ' + this.relational_match : '') + + (this._comparator ? ' :comparator ' + this._comparator : '') + ' ' + this._mailbox + ' ' + this._annotation_name + ' ' + this.key_list; @@ -124,8 +125,9 @@ export class ServerMetadataTest extends TestCommand toString() { return 'servermetadata ' - + ' ' + this.match_type - + (this.comparator ? ' :comparator ' + this.comparator : '') + + ' ' + this._match_type + + (this.relational_match ? ' ' + this.relational_match : '') + + (this._comparator ? ' :comparator ' + this._comparator : '') + ' ' + this._annotation_name + ' ' + this.key_list; } diff --git a/dev/Sieve/Grammar.js b/dev/Sieve/Grammar.js index 60f39cabd..595a34bda 100644 --- a/dev/Sieve/Grammar.js +++ b/dev/Sieve/Grammar.js @@ -9,7 +9,7 @@ import { MULTILINE_DOTSTART } from 'Sieve/RegEx'; -import { arrayToString, getMatchTypes } from 'Sieve/Utils'; +import { arrayToString, getMatchTypes, getComparators } from 'Sieve/Utils'; /** * abstract @@ -151,19 +151,64 @@ export /*abstract*/ class TestCommand extends GrammarCommand */ super(identifier); // Almost every test has a comparator and match_type, so define them here - this.comparator = ''; - this.match_type = ':is'; + this._comparator = ''; + this._match_type = ''; + this.relational_match = ''; // GrammarQuotedString DQUOTE ( "gt" / "ge" / "lt" / "le" / "eq" / "ne" ) DQUOTE + } + + get require() { return /:value|:count/.test(this._match_type) ? 'relational' : ''; } + + get match_type() + { + return this._match_type; + } + set match_type(value) + { + // default? + if (':is' == value) { + value = ''; + } + if (value.length && !getMatchTypes(0).includes(value)) { + throw 'Unsupported match-type ' + value; + } + if (':list' == value) { + this._comparator = ''; + } + if (':count' != value && ':value' != value) { + this.relational_match = ''; + } + this._match_type = value; + } + + get comparator() + { + return this._comparator; + } + set comparator(value) + { + if (!(value instanceof GrammarQuotedString)) { + value = new GrammarQuotedString(value); + } + // default? + if (value.length && 'i;ascii-casemap' != value.value) { + if (':list' == this._match_type) { + throw 'Comparator not allowed when using :list'; + } + if (!getComparators().includes(value.value)) { + throw 'Unsupported comparator ' + value; + } + this._comparator = value; + } else { + this._comparator = ''; + } } toString() { - // https://datatracker.ietf.org/doc/html/rfc6134#section-2.3 - if (!getMatchTypes().includes(this.match_type)) { - throw 'Unsupported match-type ' + this.match_type; - } return (this.identifier - + (this.comparator ? ' :comparator ' + this.comparator : '') - + (this.match_type ? ' ' + this.match_type : '') + + (this._comparator ? ' :comparator ' + this._comparator : '') + + (this._match_type ? ' ' + this._match_type : '') + + (this.relational_match ? ' ' + this.relational_match : '') + ' ' + arrayToString(this.arguments, ' ')).trim(); } } diff --git a/dev/Sieve/Parser.js b/dev/Sieve/Parser.js index 8ef1dacc3..8515d28b6 100644 --- a/dev/Sieve/Parser.js +++ b/dev/Sieve/Parser.js @@ -119,18 +119,20 @@ export const parseScript = (script, name = 'script.sieve') => { let prev_arg = args[args.length-1]; if (getMatchTypes(0).includes(arg)) { command.match_type = arg; - } else if (':value' === prev_arg || ':count' === prev_arg) { - // Sieve relational [RFC5231] match types - /^(gt|ge|lt|le|eq|ne)$/.test(arg.value) || error('Invalid relational match-type ' + arg); - command.match_type = prev_arg + ' ' + arg; + } else if (getMatchTypes(0).includes(prev_arg)) { --args.length; -// requires.push('relational'); + if (':value' === prev_arg || ':count' === prev_arg) { + // Sieve relational [RFC5231] match types + /^"(gt|ge|lt|le|eq|ne)"$/.test(arg) || error('Invalid relational match-type ' + arg); + command.relational_match = arg; +// requires.push('relational'); + return; + } } else if (':comparator' === prev_arg) { command.comparator = arg; --args.length; - } else { - args.push(arg); } + args.push(arg); }, pushArgs = () => { if (args.length) { diff --git a/dev/Sieve/Utils.js b/dev/Sieve/Utils.js index 266857aec..5f5d13f07 100644 --- a/dev/Sieve/Utils.js +++ b/dev/Sieve/Utils.js @@ -58,13 +58,33 @@ export const serverErrorDesc(text); }, - getComparators = () => ['i;ascii-casemap'], + getComparators = (validOnly = 0) => { + let result = [ + // Default + 'i;ascii-casemap', + ]; + if (capa.includes('relational') || !validOnly) { + result.push('i;octet'); + } + if (capa.includes('comparator-i;ascii-numeric') || !validOnly) { + result.push('i;ascii-numeric'); + } + if (capa.includes('comparator-i;unicode-casemap') || !validOnly) { + result.push('i;unicode-casemap'); + } + return result; + }, getMatchTypes = (validOnly = 1) => { let result = [':is',':contains',':matches']; // https://datatracker.ietf.org/doc/html/rfc6134#section-2.3 + // Only available for tests with a key_list property if (capa.includes('extlists') || !validOnly) { result.push(':list'); } + if (capa.includes('relational') || !validOnly) { + result.push(':value'); + result.push(':count'); + } return result; };