From f7b1981bcce724e58ff0ae3926c7c513724e98a0 Mon Sep 17 00:00:00 2001 From: hguilbert <51283484+hguilbert@users.noreply.github.com> Date: Sun, 22 Sep 2024 07:12:04 +0200 Subject: [PATCH 1/6] Update user.json --- snappymail/v/0.0.0/app/localization/fr/user.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/snappymail/v/0.0.0/app/localization/fr/user.json b/snappymail/v/0.0.0/app/localization/fr/user.json index 0fea1c0db..1df10a859 100644 --- a/snappymail/v/0.0.0/app/localization/fr/user.json +++ b/snappymail/v/0.0.0/app/localization/fr/user.json @@ -219,9 +219,9 @@ "DISPLAY_NAME": "Nom à afficher", "LAST_NAME": "Nom", "FIRST_NAME": "Prénom", - "MIDDLE_NAME": "Middle name", - "NAME_PREFIX": "Name prefix", - "NAME_SUFFIX": "Name suffix", + "MIDDLE_NAME": "Deuxième Prénom", + "NAME_PREFIX": "Civilité", + "NAME_SUFFIX": "Suffix", "NICK_NAME": "Pseudonyme", "LABEL_READ_ONLY": "Lecture seule", "ADD_MENU_LABEL": "Ajouter", From 109a11020299f3884cde320b6765f82b4577d78e Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Sun, 22 Sep 2024 19:02:17 +0200 Subject: [PATCH 2/6] Resolve #1770 --- .docker/release/files/etc/nginx/nginx.conf | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.docker/release/files/etc/nginx/nginx.conf b/.docker/release/files/etc/nginx/nginx.conf index ed87cb201..e20252da0 100644 --- a/.docker/release/files/etc/nginx/nginx.conf +++ b/.docker/release/files/etc/nginx/nginx.conf @@ -51,7 +51,9 @@ http { image/svg+xml; server { - listen 8888; + listen 8888; + listen [::]:8888; + #listen [::]:8888 ipv6only=off; root /snappymail; index index.php; charset utf-8; From d80fe8bc7303cca7c00f4b929e5f77fffb6ea1ba Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Sun, 22 Sep 2024 19:02:57 +0200 Subject: [PATCH 3/6] fix spacing --- .docker/release/files/etc/nginx/nginx.conf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.docker/release/files/etc/nginx/nginx.conf b/.docker/release/files/etc/nginx/nginx.conf index e20252da0..aff3829cf 100644 --- a/.docker/release/files/etc/nginx/nginx.conf +++ b/.docker/release/files/etc/nginx/nginx.conf @@ -51,9 +51,9 @@ http { image/svg+xml; server { - listen 8888; - listen [::]:8888; - #listen [::]:8888 ipv6only=off; + listen 8888; + listen [::]:8888; + #listen [::]:8888 ipv6only=off; root /snappymail; index index.php; charset utf-8; From 279cad038570b24537a268c2faffbfeef4ae93c0 Mon Sep 17 00:00:00 2001 From: tinola Date: Sun, 22 Sep 2024 22:30:29 +0200 Subject: [PATCH 4/6] Update user.json --- snappymail/v/0.0.0/app/localization/pl/user.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/snappymail/v/0.0.0/app/localization/pl/user.json b/snappymail/v/0.0.0/app/localization/pl/user.json index 3bcf228f8..24ea42e1e 100644 --- a/snappymail/v/0.0.0/app/localization/pl/user.json +++ b/snappymail/v/0.0.0/app/localization/pl/user.json @@ -502,8 +502,8 @@ "IMAGES_OPTION_ALWAYS": "Zawsze pokazuj", "IMAGES_WHITELIST": "Biała lista obrazów", "MESSAGE_POPUP_WINDOW": "Pokaż w nowym oknie zamiast w nowej karcie", - "MAILTO": "Register as 'mailto:' links handler", - "HTML_TO_MD": "Convert HTML to Markdown instead of plain text" + "MAILTO": "Zarejestruj obsługę linków 'mailto:' jako domyślnego klienta poczty", + "HTML_TO_MD": "Konwertuj HTML na Markdown zamiast zwykłego tekstu" }, "SETTINGS_CONTACTS": { "LABEL_CONTACTS_AUTOSAVE": "Automatycznie dodawaj odbiorców do książki adresowej", From c773b852a1aad6bbf3823e8c9f5bc4ba129dfa98 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Mon, 23 Sep 2024 10:48:22 +0200 Subject: [PATCH 5/6] Improve sieve parser --- dev/Sieve/Commands.js | 2 - dev/Sieve/Commands/Actions.js | 12 ++-- dev/Sieve/Commands/Controls.js | 61 ++++++++++++------- dev/Sieve/Commands/Tests.js | 6 +- dev/Sieve/Extensions/rfc5230.js | 1 - dev/Sieve/Extensions/rfc5232.js | 9 +-- dev/Sieve/Extensions/rfc5429.js | 8 +-- dev/Sieve/Extensions/rfc5703.js | 11 +++- dev/Sieve/Grammar.js | 105 ++++++++++++++++---------------- dev/Sieve/Parser.js | 11 ++-- dev/Sieve/Utils.js | 4 +- 11 files changed, 124 insertions(+), 106 deletions(-) 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 From 257d9201f6245028733d909608ded942f9d67058 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Mon, 23 Sep 2024 11:23:48 +0200 Subject: [PATCH 6/6] Improved Sieve parser validation --- dev/Sieve/Grammar.js | 10 +++++++++- dev/Sieve/Parser.js | 19 +++++++++++++------ .../Views/User/PopupsSieveScript.html | 2 +- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/dev/Sieve/Grammar.js b/dev/Sieve/Grammar.js index 5fb21c71e..a113374c5 100644 --- a/dev/Sieve/Grammar.js +++ b/dev/Sieve/Grammar.js @@ -45,8 +45,16 @@ export class GrammarString /*extends String*/ /** * abstract */ -export class GrammarComment extends GrammarString +export /*abstract*/ class GrammarComment extends GrammarString { +/* + constructor() + { + if (this.constructor == GrammarComment) { + throw Error("Abstract class can't be instantiated."); + } + } +*/ } /** diff --git a/dev/Sieve/Parser.js b/dev/Sieve/Parser.js index 6d36eea04..9f0892061 100644 --- a/dev/Sieve/Parser.js +++ b/dev/Sieve/Parser.js @@ -18,6 +18,7 @@ import { import { GrammarBracketComment, GrammarCommand, + GrammarComment, GrammarHashComment, GrammarMultiLine, GrammarNumber, @@ -28,7 +29,7 @@ import { } from 'Sieve/Grammar'; import { availableCommands } from 'Sieve/Commands'; -import { ConditionalCommand, RequireCommand } from 'Sieve/Commands/Controls'; +import { ConditionalCommand, IfCommand, RequireCommand } from 'Sieve/Commands/Controls'; import { NotTest } from 'Sieve/Commands/Tests'; const @@ -156,10 +157,17 @@ export const parseScript = (script, name = 'script.sieve') => { let new_command; 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'); + let valid = false, cmd = (command ? command?.commands : tree), i = cmd?.length; + while (i) { + cmd[--i]; + if (cmd[i] instanceof IfCommand) { + valid = true; + break; + } else if (typeof cmd[i] !== 'string' && !(cmd[i] instanceof GrammarComment)) { + break; + } + } + valid || error('Not after IF/ELSIF condition'); } new_command = new Commands[value](); } else { @@ -266,7 +274,6 @@ export const parseScript = (script, name = 'script.sieve') => { break; case T_BLOCK_END: (command instanceof ConditionalCommand) || error(TokenError[type]); -// prev_command = command; command = levels.up(); break; diff --git a/snappymail/v/0.0.0/app/templates/Views/User/PopupsSieveScript.html b/snappymail/v/0.0.0/app/templates/Views/User/PopupsSieveScript.html index 2ebd054d1..3c0e2ff12 100644 --- a/snappymail/v/0.0.0/app/templates/Views/User/PopupsSieveScript.html +++ b/snappymail/v/0.0.0/app/templates/Views/User/PopupsSieveScript.html @@ -11,7 +11,7 @@