diff --git a/dev/Sieve/Commands.js b/dev/Sieve/Commands.js index 45c2199c2..941b4e409 100644 --- a/dev/Sieve/Commands.js +++ b/dev/Sieve/Commands.js @@ -15,7 +15,6 @@ import { } from 'Sieve/Commands/Actions'; import { - ConditionalCommand, ElsIfCommand, ElseCommand, IfCommand, @@ -67,7 +66,6 @@ export const IfCommand, ElsIfCommand, ElseCommand, - ConditionalCommand, RequireCommand, StopCommand, // Action commands diff --git a/dev/Sieve/Commands/Actions.js b/dev/Sieve/Commands/Actions.js index 3d0665a7d..d38f5bcc3 100644 --- a/dev/Sieve/Commands/Actions.js +++ b/dev/Sieve/Commands/Actions.js @@ -21,6 +21,10 @@ export class FileIntoCommand extends ActionCommand super(); // QuotedString / MultiLine this._mailbox = new GrammarQuotedString(); + // https://datatracker.ietf.org/doc/html/rfc3894 + this.copy = false; + // https://datatracker.ietf.org/doc/html/rfc5490#section-3.2 + this.create = false; } get require() { return 'fileinto'; } @@ -28,9 +32,7 @@ export class FileIntoCommand extends ActionCommand toString() { return 'fileinto' - // https://datatracker.ietf.org/doc/html/rfc3894 + ((this.copy && capa.includes('copy')) ? ' :copy' : '') - // https://datatracker.ietf.org/doc/html/rfc5490#section-3.2 + ((this.create && capa.includes('mailbox')) ? ' :create' : '') + ' ' + this._mailbox + ';'; @@ -64,15 +66,17 @@ export class RedirectCommand extends ActionCommand super(); // QuotedString / MultiLine this._address = new GrammarQuotedString(); + // https://datatracker.ietf.org/doc/html/rfc3894 + this.copy = false; + // https://datatracker.ietf.org/doc/html/rfc6134#section-2.3 + this.list = null; } toString() { return 'redirect' - // https://datatracker.ietf.org/doc/html/rfc6134#section-2.3 // + ((this.list && capa.includes('extlists')) ? ' :list ' + this.list : '') - // https://datatracker.ietf.org/doc/html/rfc3894 + ((this.copy && capa.includes('copy')) ? ' :copy' : '') + ' ' + this._address + ';'; diff --git a/dev/Sieve/Commands/Controls.js b/dev/Sieve/Commands/Controls.js index 20144d457..a82ed6f9a 100644 --- a/dev/Sieve/Commands/Controls.js +++ b/dev/Sieve/Commands/Controls.js @@ -5,6 +5,7 @@ import { ControlCommand, + GrammarCommands, GrammarStringList, GrammarQuotedString } from 'Sieve/Grammar'; @@ -16,37 +17,55 @@ import { * elsif * else */ -export class ConditionalCommand extends ControlCommand +export /*abstract*/ class ConditionalCommand extends ControlCommand { - constructor(identifier) + constructor() { - super(identifier); - this.test = null; - } - - toString() - { - return this.identifier + ' ' + this.test + ' ' + this.commands; - } /* - public function pushArguments(array $args): void - { - args.forEach((arg, i) => { - if (i && ':' === args[i-1][0]) { - this[args[i-1].replace(':','_')].value = arg.value; - } - }); - print_r($args); - exit; - } + if (this.constructor == ConditionalCommand) { + throw Error("Abstract class can't be instantiated."); + } */ + super(); + this.commands = new GrammarCommands; + } } export class IfCommand extends ConditionalCommand { + constructor() + { + super(); + this._test = null; // must be descendent instanceof TestCommand + } + + get test() + { + return this._test; + } + + set test(value) + { +/* + if (!value instanceof TestCommand) { + throw Error("test must be descendent instanceof TestCommand."); + } +*/ + this._test = value; + } + + toString() + { +/* + if (!this._test instanceof TestCommand) { + throw Error("test must be descendent instanceof TestCommand."); + } +*/ + return this.identifier + ' ' + this._test + ' ' + this.commands; + } } -export class ElsIfCommand extends ConditionalCommand +export class ElsIfCommand extends IfCommand { } diff --git a/dev/Sieve/Commands/Tests.js b/dev/Sieve/Commands/Tests.js index 6e5c8c05a..f566fadae 100644 --- a/dev/Sieve/Commands/Tests.js +++ b/dev/Sieve/Commands/Tests.js @@ -230,6 +230,7 @@ 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; @@ -316,11 +317,6 @@ export class NotTest extends TestCommand { return 'not ' + this.test; } - - pushArguments() - { - throw 'No arguments'; - } } /** diff --git a/dev/Sieve/Extensions/rfc5230.js b/dev/Sieve/Extensions/rfc5230.js index 1bc340236..9dd99e1e1 100644 --- a/dev/Sieve/Extensions/rfc5230.js +++ b/dev/Sieve/Extensions/rfc5230.js @@ -43,7 +43,6 @@ export class VacationCommand extends ActionCommand } if (this._from.length) { result += ' :from ' + this._from; -// result += ' :from ' + this.arguments[':from']; } if (this.addresses.length) { result += ' :addresses ' + this.addresses; diff --git a/dev/Sieve/Extensions/rfc5232.js b/dev/Sieve/Extensions/rfc5232.js index 002ad0b7a..0577cde1d 100644 --- a/dev/Sieve/Extensions/rfc5232.js +++ b/dev/Sieve/Extensions/rfc5232.js @@ -43,11 +43,12 @@ class FlagCommand extends ActionCommand if (args[0] instanceof GrammarQuotedString) { this._variablename = args[0]; } - if (args[1] instanceof GrammarString) { - this.list_of_flags = args[1]; - } - } else if (args[0] instanceof GrammarString) { + args[0] = args[1]; + } + if (args[0] instanceof GrammarStringList) { this.list_of_flags = args[0]; + } else if (args[0]) { + this.list_of_flags.push(args[0]); } } } diff --git a/dev/Sieve/Extensions/rfc5429.js b/dev/Sieve/Extensions/rfc5429.js index 984fa2eb9..d33aead7c 100644 --- a/dev/Sieve/Extensions/rfc5429.js +++ b/dev/Sieve/Extensions/rfc5429.js @@ -8,11 +8,11 @@ import { GrammarString } from 'Sieve/Grammar'; -class rfc5429Command extends ActionCommand +class /*abstract*/ rfc5429Command extends ActionCommand { - constructor(identifier) + constructor() { - super(identifier); + super(); this._reason = new GrammarQuotedString; } @@ -44,7 +44,6 @@ class rfc5429Command extends ActionCommand */ export class ErejectCommand extends rfc5429Command { - constructor() { super('ereject'); } get require() { return 'ereject'; } } @@ -53,6 +52,5 @@ export class ErejectCommand extends rfc5429Command */ export class RejectCommand extends rfc5429Command { - constructor() { super('reject'); } get require() { return 'reject'; } } diff --git a/dev/Sieve/Extensions/rfc5703.js b/dev/Sieve/Extensions/rfc5703.js index c0b5322e8..f131da33a 100644 --- a/dev/Sieve/Extensions/rfc5703.js +++ b/dev/Sieve/Extensions/rfc5703.js @@ -5,6 +5,7 @@ import { ActionCommand, ControlCommand, + GrammarCommands, GrammarNumber, GrammarQuotedString, GrammarString, @@ -20,6 +21,7 @@ export class ForEveryPartCommand extends ControlCommand { super(); this._name = new GrammarString; + this.commands = new GrammarCommands; } get require() { return 'foreverypart'; } @@ -27,7 +29,7 @@ export class ForEveryPartCommand extends ControlCommand toString() { let result = 'foreverypart'; - if (this._subject.length) { + if (this._name.length) { result += ' :name ' + this._name; } return result + ' ' + this.commands; @@ -43,12 +45,15 @@ export class ForEveryPartCommand extends ControlCommand } } +/** + * Must be inside foreverypart + */ export class BreakCommand extends ForEveryPartCommand { toString() { let result = 'break'; - if (this._subject.length) { + if (this._name.length) { result += ' :name ' + this._name; } return result + ';'; @@ -82,7 +87,6 @@ export class ReplaceCommand extends ActionCommand } if (this._from.length) { result += ' :from ' + this._from; -// result += ' :from ' + this.arguments[':from']; } return result + this.replacement + ';'; } @@ -140,6 +144,7 @@ export class EncloseCommand extends ActionCommand /** * https://datatracker.ietf.org/doc/html/rfc5703#section-7 + * Should be inside foreverypart, else empty and flagged as a compilation error */ export class ExtractTextCommand extends ActionCommand { diff --git a/dev/Sieve/Grammar.js b/dev/Sieve/Grammar.js index 95a142c11..5fb21c71e 100644 --- a/dev/Sieve/Grammar.js +++ b/dev/Sieve/Grammar.js @@ -18,7 +18,7 @@ export class GrammarString /*extends String*/ { constructor(value = '') { - this._value = value; + this._value = value.toString ? value.toString() : value; } toString() @@ -52,34 +52,26 @@ export class GrammarComment extends GrammarString /** * https://tools.ietf.org/html/rfc5228#section-2.9 */ -export class GrammarCommand +const cmdNameSuffix = /(test|command|action)$/; +export /*abstract*/ class GrammarCommand { constructor(identifier) { - this.identifier = identifier || this.constructor.name.toLowerCase().replace(/(test|command|action)$/, ''); - this.arguments = []; - this.commands = new GrammarCommands; +/* + if (this.constructor == GrammarCommand) { + throw Error("Abstract class can't be instantiated."); + } +*/ + this.identifier = identifier || this.constructor.name.toLowerCase().replace(cmdNameSuffix, ''); } toString() { let result = this.identifier; - if (this.arguments.length) { + if (this.arguments?.length) { result += ' ' + arrayToString(this.arguments, ' '); } - return result + ( - this.commands.length ? ' ' + this.commands : ';' - ); - } - - getComparators() - { - return ['i;ascii-casemap']; - } - - getMatchTypes() - { - return [':is', ':contains', ':matches']; + return result + ';'; } pushArguments(args) @@ -108,63 +100,47 @@ export class GrammarCommands extends Array /** * https://tools.ietf.org/html/rfc5228#section-3 */ -export class ControlCommand extends GrammarCommand +export /*abstract*/ class ControlCommand extends GrammarCommand { +/* constructor(identifier) { - super(identifier); - this.commands = new GrammarCommands; - } - - toString() - { - let result = this.identifier; - if (this.arguments.length) { - result += ' ' + arrayToString(this.arguments, ' '); + if (this.constructor == ControlCommand) { + throw Error("Abstract class can't be instantiated."); } - return result + ( - this.commands.length ? ' ' + this.commands : ';' - ); - } - - getComparators() - { - return ['i;ascii-casemap']; - } - - getMatchTypes() - { - return [':is', ':contains', ':matches']; + super(identifier); } +*/ } /** * https://tools.ietf.org/html/rfc5228#section-4 */ -export class ActionCommand extends GrammarCommand +export /*abstract*/ class ActionCommand extends GrammarCommand { +/* constructor(identifier) { + if (this.constructor == ActionCommand) { + throw Error("Abstract class can't be instantiated."); + } super(identifier); } - - toString() - { - let result = this.identifier; - if (this.arguments.length) { - result += ' ' + arrayToString(this.arguments, ' '); - } - return result + ';' - } +*/ } /** * https://tools.ietf.org/html/rfc5228#section-5 */ -export class TestCommand extends GrammarCommand +export /*abstract*/ class TestCommand extends GrammarCommand { constructor(identifier) { +/* + if (this.constructor == TestCommand) { + throw Error("Abstract class can't be instantiated."); + } +*/ super(identifier); // Almost every test has a comparator and match_type, so define them here this.comparator = ''; @@ -251,6 +227,7 @@ export class GrammarStringList extends Array { toString() { + // if there is only a single string, the brackets are optional if (1 < this.length) { return '[' + this.join(',') + ']'; } @@ -259,7 +236,7 @@ export class GrammarStringList extends Array push(value) { - if (!(value instanceof GrammarString)) { + if (!(value instanceof GrammarQuotedString)) { value = new GrammarQuotedString(value); } super.push(value); @@ -324,3 +301,23 @@ GrammarMultiLine.fromString = string => { } return new GrammarMultiLine(); } + +export class UnknownCommand extends GrammarCommand +{ + constructor(identifier) + { + super(identifier); + this.commands = new GrammarCommands; + } + + toString() + { + let result = this.identifier; + if (this.arguments?.length) { + result += ' ' + arrayToString(this.arguments, ' '); + } + return result + ( + this.commands?.length ? ' ' + this.commands : ';' + ); + } +} diff --git a/dev/Sieve/Parser.js b/dev/Sieve/Parser.js index ce929d24e..6d36eea04 100644 --- a/dev/Sieve/Parser.js +++ b/dev/Sieve/Parser.js @@ -154,12 +154,10 @@ export const parseScript = (script, name = 'script.sieve') => { pushArgs(); value = value.toLowerCase(); let new_command; - if ('if' === value) { - new_command = new ConditionalCommand(value); - } else if ('elsif' === value || 'else' === value) { -// (prev_command instanceof ConditionalCommand) || error('Not after IF condition'); - new_command = new ConditionalCommand(value); - } else if (Commands[value]) { + if (Commands[value]) { + if ('elsif' === value || 'else' === value) { +// (prev_command instanceof ConditionalCommand) || error('Not after IF condition'); + } if ('allof' === value || 'anyof' === value) { // (command instanceof ConditionalCommand || command instanceof NotTest) || error('Test-list not in conditional'); } @@ -293,5 +291,6 @@ export const parseScript = (script, name = 'script.sieve') => { } tree.requires = requires; + tree.toString = () => tree.join('\r\n'); return tree; }; diff --git a/dev/Sieve/Utils.js b/dev/Sieve/Utils.js index 14c58101a..266857aec 100644 --- a/dev/Sieve/Utils.js +++ b/dev/Sieve/Utils.js @@ -22,7 +22,7 @@ export const koComputable = fn => ko.computed(fn, {'pure':true}), arrayToString = (arr, separator) => - arr.map(item => item.toString?.() || item).join(separator), + (arr || []).map(item => item.toString?.() || item).join(separator), /* getNotificationMessage = code => { let key = getKeyByValue(Notifications, code); @@ -58,6 +58,8 @@ export const serverErrorDesc(text); }, + getComparators = () => ['i;ascii-casemap'], + getMatchTypes = (validOnly = 1) => { let result = [':is',':contains',':matches']; // https://datatracker.ietf.org/doc/html/rfc6134#section-2.3