mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-11 00:14:50 +03:00
More Sieve parser improvements after doing 'if not' test
This commit is contained in:
parent
2786d0d959
commit
0aafce4214
4 changed files with 153 additions and 130 deletions
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
(Sieve => {
|
||||
|
||||
const Grammar = Sieve.Grammar, Command = Grammar.Command;
|
||||
|
||||
/**
|
||||
* https://tools.ietf.org/html/rfc5228#section-3.1
|
||||
* Usage:
|
||||
|
|
@ -11,23 +13,19 @@
|
|||
* elsif <test2: test> <block2: block>
|
||||
* else <block3: block>
|
||||
*/
|
||||
class Conditional
|
||||
class Conditional extends Command
|
||||
{
|
||||
constructor(identifier = 'if')
|
||||
{
|
||||
this.identifier = identifier;
|
||||
this.test = null,
|
||||
this.commands = [];
|
||||
super(identifier);
|
||||
this.test = null;
|
||||
}
|
||||
|
||||
toString()
|
||||
{
|
||||
return this.identifier
|
||||
+ ('else' !== this.identifier ? ' ' + this.test : '')
|
||||
+ (this.commands.length
|
||||
? ' {\r\n\t' + Sieve.arrayToString(this.commands, ';\r\n\t') + ';\r\n}'
|
||||
: ';'
|
||||
);
|
||||
+ ' ' + this.commands;
|
||||
}
|
||||
/*
|
||||
public function pushArguments(array $args): void
|
||||
|
|
@ -41,23 +39,24 @@ class Conditional
|
|||
/**
|
||||
* https://tools.ietf.org/html/rfc5228#section-3.2
|
||||
*/
|
||||
class Require /* extends Array*/
|
||||
class Require extends Command
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
this.capabilities = new Sieve.Grammar.StringList();
|
||||
super('require');
|
||||
this.capabilities = new Grammar.StringList();
|
||||
}
|
||||
|
||||
toString()
|
||||
{
|
||||
return 'require ' + this.capabilities.toString();
|
||||
return 'require ' + this.capabilities.toString() + ';';
|
||||
}
|
||||
|
||||
pushArguments(args)
|
||||
{
|
||||
if (args[0] instanceof Sieve.Grammar.StringList) {
|
||||
if (args[0] instanceof Grammar.StringList) {
|
||||
this.capabilities = args[0];
|
||||
} else if (args[0] instanceof Sieve.Grammar.QuotedString) {
|
||||
} else if (args[0] instanceof Grammar.QuotedString) {
|
||||
this.capabilities.push(args[0]);
|
||||
}
|
||||
}
|
||||
|
|
@ -66,30 +65,36 @@ class Require /* extends Array*/
|
|||
/**
|
||||
* https://tools.ietf.org/html/rfc5228#section-3.3
|
||||
*/
|
||||
class Stop
|
||||
class Stop extends Command
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super('stop');
|
||||
}
|
||||
|
||||
toString()
|
||||
{
|
||||
return 'stop';
|
||||
return 'stop;';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* https://tools.ietf.org/html/rfc5228#section-4.1
|
||||
*/
|
||||
class FileInto
|
||||
class FileInto extends Command
|
||||
{
|
||||
// const REQUIRE = 'fileinto';
|
||||
|
||||
constructor()
|
||||
{
|
||||
super('fileinto');
|
||||
// QuotedString / MultiLine
|
||||
this._mailbox = new Sieve.Grammar.QuotedString();
|
||||
this._mailbox = new Grammar.QuotedString();
|
||||
}
|
||||
|
||||
toString()
|
||||
{
|
||||
return 'fileinto ' + this._mailbox;
|
||||
return 'fileinto ' + this._mailbox + ';';
|
||||
}
|
||||
|
||||
get mailbox()
|
||||
|
|
@ -104,7 +109,7 @@ class FileInto
|
|||
|
||||
pushArguments(args)
|
||||
{
|
||||
if (args[0] instanceof Sieve.Grammar.StringType) {
|
||||
if (args[0] instanceof Grammar.StringType) {
|
||||
this._mailbox = args[0];
|
||||
}
|
||||
}
|
||||
|
|
@ -113,17 +118,18 @@ class FileInto
|
|||
/**
|
||||
* https://tools.ietf.org/html/rfc5228#section-4.2
|
||||
*/
|
||||
class Redirect
|
||||
class Redirect extends Command
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super('redirect');
|
||||
// QuotedString / MultiLine
|
||||
this._address = new Sieve.Grammar.QuotedString();
|
||||
this._address = new Grammar.QuotedString();
|
||||
}
|
||||
|
||||
toString()
|
||||
{
|
||||
return 'redirect ' + this.address;
|
||||
return 'redirect ' + this.address + ';';
|
||||
}
|
||||
|
||||
get address()
|
||||
|
|
@ -140,22 +146,32 @@ class Redirect
|
|||
/**
|
||||
* https://tools.ietf.org/html/rfc5228#section-4.3
|
||||
*/
|
||||
class Keep
|
||||
class Keep extends Command
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super('keep');
|
||||
}
|
||||
|
||||
toString()
|
||||
{
|
||||
return 'keep';
|
||||
return 'keep;';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* https://tools.ietf.org/html/rfc5228#section-4.4
|
||||
*/
|
||||
class Discard
|
||||
class Discard extends Command
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super('discard');
|
||||
}
|
||||
|
||||
toString()
|
||||
{
|
||||
return 'discard';
|
||||
return 'discard;';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,33 +49,23 @@ class Comment extends StringType
|
|||
*/
|
||||
class Command
|
||||
{
|
||||
constructor(identifier, text = null)
|
||||
constructor(identifier)
|
||||
{
|
||||
this.identifier = identifier;
|
||||
this.arguments = [];
|
||||
if (text) {
|
||||
this.text = text;
|
||||
}
|
||||
this.commands = new Commands;
|
||||
}
|
||||
|
||||
toString()
|
||||
{
|
||||
let result = this.identifier;
|
||||
if ('anyof' === result || 'allof' === result) {
|
||||
result += ' (\r\n\t' + Sieve.arrayToString(this.commands, ',\r\n\t') + '\r\n)';
|
||||
if (this.arguments.length) {
|
||||
result += Sieve.arrayToString(this.arguments, ' ');
|
||||
}
|
||||
if (this.commands.length) {
|
||||
result += ' ' + this.commands.toString();
|
||||
} else {
|
||||
this.arguments.forEach(arg => {
|
||||
if (Array.isArray(arg)) {
|
||||
result += ' [' + arg.join(',') + ']';
|
||||
} else {
|
||||
result += ' ' + arg;
|
||||
}
|
||||
});
|
||||
if (this.commands) {
|
||||
result += ' {\r\n\t' + Sieve.arrayToString(this.commands, ';\r\n\t') + '\r\n}';
|
||||
} else {
|
||||
result += ';';
|
||||
}
|
||||
result += ';';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
@ -96,24 +86,22 @@ class Command
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
class Commands extends Array
|
||||
{
|
||||
toString()
|
||||
{
|
||||
return this.length
|
||||
? '{\r\n\t' + Sieve.arrayToString(this, ';\r\n\t') + ';\r\n}'
|
||||
: ';';
|
||||
? '{\r\n\t' + Sieve.arrayToString(this, '\r\n\t') + '\r\n}'
|
||||
: '{}';
|
||||
}
|
||||
|
||||
push(value)
|
||||
{
|
||||
if (value instanceof Command) {
|
||||
if (value instanceof Command || value instanceof Comment) {
|
||||
super.push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
class BracketComment extends Comment
|
||||
{
|
||||
|
|
@ -223,7 +211,7 @@ class MultiLine extends StringType
|
|||
|
||||
|
||||
/**
|
||||
* https://tools.ietf.org/html/rfc5228#section-2.9
|
||||
* https://tools.ietf.org/html/rfc5228#section-5
|
||||
*/
|
||||
class Test
|
||||
{
|
||||
|
|
@ -250,15 +238,15 @@ class Test
|
|||
*/
|
||||
class TestList extends Array
|
||||
{
|
||||
constructor(identifier)
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
this.identifier = identifier; // allof / anyof
|
||||
}
|
||||
|
||||
toString()
|
||||
{
|
||||
if (1 < this.length) {
|
||||
// return '(\r\n\t' + Sieve.arrayToString(this, ',\r\n\t') + '\r\n)';
|
||||
return '(' + this.join(', ') + ')';
|
||||
}
|
||||
return this.length ? this[0] : '';
|
||||
|
|
@ -266,34 +254,13 @@ class TestList extends Array
|
|||
|
||||
push(value)
|
||||
{
|
||||
/*
|
||||
if (!(value instanceof Command)) {
|
||||
value = new Command($value);
|
||||
if (!(value instanceof Test)) {
|
||||
throw 'Not an instanceof Test';
|
||||
}
|
||||
*/
|
||||
super.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
TestList.fromString = list => {
|
||||
let test,
|
||||
obj = new TestList,
|
||||
regex = RegExp('(?:^\\s*|\\s*,\\s*)(' + RegEx.IDENTIFIER + ')((?:\\s+' + RegEx.ARGUMENT + ')*)', 'g');
|
||||
list = list.replace(/^[\r\n\t(]+/, '');
|
||||
while ((test = regex.exec(list))) {
|
||||
let command = new Sieve.Commands.Command(test[1]);
|
||||
obj.push(command);
|
||||
/*
|
||||
if (\preg_match_all('@\\s+(' . RegEx.ARGUMENT . ')@', $test[2], $args, PREG_SET_ORDER)) {
|
||||
foreach ($args as $arg) {
|
||||
$command->arguments[] = $arg;
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
Sieve.Grammar = {
|
||||
Command: Command,
|
||||
BracketComment: BracketComment,
|
||||
|
|
|
|||
|
|
@ -106,31 +106,32 @@ Sieve.parseScript = script => {
|
|||
// new_command = new Sieve.Grammar.Command(value);
|
||||
}
|
||||
|
||||
if (command instanceof Sieve.Commands.Conditional && !command.test) {
|
||||
if (new_command instanceof Sieve.Grammar.TestList) {
|
||||
levels.push(new_command);
|
||||
if (new_command instanceof Sieve.Grammar.Test) {
|
||||
if (command instanceof Sieve.Commands.Conditional || command instanceof Sieve.Tests.Not) {
|
||||
// if/elsif/else new_command
|
||||
// not new_command
|
||||
command.test = new_command;
|
||||
} else if (command.tests instanceof Sieve.Grammar.TestList) {
|
||||
// allof/anyof .tests[] new_command
|
||||
command.tests.push(new_command);
|
||||
} else {
|
||||
new_command = new Sieve.Grammar.Test(value);
|
||||
error('Test not allowed here');
|
||||
}
|
||||
} else if (command) {
|
||||
if (command.commands) {
|
||||
command.commands.push(new_command);
|
||||
} else {
|
||||
console.dir(command);
|
||||
error('commands not allowed');
|
||||
}
|
||||
command.test = new_command;
|
||||
} else {
|
||||
if (levels.length) {
|
||||
let cmd = levels.last();
|
||||
if (cmd instanceof Sieve.Grammar.TestList) {
|
||||
cmd.push(new_command);
|
||||
} else {
|
||||
cmd.commands.push(new_command);
|
||||
}
|
||||
} else {
|
||||
tree.push(new_command);
|
||||
}
|
||||
if (new_command instanceof Sieve.Commands.Conditional || new_command instanceof Sieve.Grammar.TestList) {
|
||||
levels.push(new_command);
|
||||
}
|
||||
tree.push(new_command);
|
||||
}
|
||||
levels.push(new_command);
|
||||
command = new_command;
|
||||
break; }
|
||||
|
||||
// Arguments
|
||||
case T_TAG:
|
||||
command
|
||||
? args.push(value.toLowerCase())
|
||||
|
|
@ -157,6 +158,7 @@ Sieve.parseScript = script => {
|
|||
: error('Number must be command argument');
|
||||
break;
|
||||
|
||||
// Comments
|
||||
case T_BRACKET_COMMENT:
|
||||
(command ? command.commands : tree).push(
|
||||
new Sieve.Grammar.BracketComment(value.substr(2, value.length-4))
|
||||
|
|
@ -174,48 +176,52 @@ Sieve.parseScript = script => {
|
|||
command || tree.push(value.trim());
|
||||
break;
|
||||
|
||||
// Command end
|
||||
case T_SEMICOLON:
|
||||
command || error('Semicolon not at end of command');
|
||||
pushArgs();
|
||||
// levels.pop();
|
||||
levels.pop();
|
||||
command = levels.last();
|
||||
break;
|
||||
|
||||
// Command block
|
||||
case T_BLOCK_START:
|
||||
pushArgs();
|
||||
// https://tools.ietf.org/html/rfc5228#section-2.9
|
||||
// Action commands do not take tests or blocks
|
||||
levels.length || error('Block start not part of control command');
|
||||
command || error('Block start not at end of command arguments');
|
||||
pushArgs();
|
||||
command = levels.last();
|
||||
while (command && !(command instanceof Sieve.Commands.Conditional)) {
|
||||
levels.pop();
|
||||
command = levels.last();
|
||||
}
|
||||
command || error('Block start not part of control command');
|
||||
break;
|
||||
case T_BLOCK_END:
|
||||
levels.length || error('Block end has no matching block start');
|
||||
(command instanceof Sieve.Commands.Conditional) || error('Block end has no matching block start');
|
||||
levels.pop();
|
||||
command = levels.last();
|
||||
break;
|
||||
|
||||
// anyof / allof
|
||||
// anyof / allof ( ... , ... )
|
||||
case T_LEFT_PARENTHESIS:
|
||||
(levels.last() instanceof Sieve.Grammar.TestList)
|
||||
|| error('Test start not part of test-list');
|
||||
command || error('Not inside command');
|
||||
pushArgs();
|
||||
command = levels.last();
|
||||
while (command && !(command.tests instanceof Sieve.Grammar.TestList)) {
|
||||
levels.pop();
|
||||
command = levels.last();
|
||||
}
|
||||
command || error('Test start not part of anyof/allof test');
|
||||
break;
|
||||
case T_RIGHT_PARENTHESIS:
|
||||
(levels.last() instanceof Sieve.Grammar.TestList)
|
||||
|| error('Test end not part of test-list');
|
||||
pushArgs();
|
||||
levels.pop();
|
||||
command = levels.last();
|
||||
(command.tests instanceof Sieve.Grammar.TestList) || error('Test end not part of test-list');
|
||||
break;
|
||||
case T_COMMA:
|
||||
// Must be inside PARENTHESIS aka test-list
|
||||
(levels.last() instanceof Sieve.Grammar.TestList)
|
||||
|| error('Comma not part of test-list');
|
||||
pushArgs();
|
||||
levels.pop();
|
||||
command = levels.last();
|
||||
// Must be inside PARENTHESIS aka test-list
|
||||
(command.tests instanceof Sieve.Grammar.TestList) || error('Comma not part of test-list');
|
||||
break;
|
||||
|
||||
case T_UNKNOWN:
|
||||
|
|
|
|||
|
|
@ -4,18 +4,23 @@
|
|||
|
||||
(Sieve => {
|
||||
|
||||
const Grammar = Sieve.Grammar,
|
||||
Test = Grammar.Test,
|
||||
StringList = Grammar.StringList;
|
||||
|
||||
/**
|
||||
* https://tools.ietf.org/html/rfc5228#section-5.1
|
||||
*/
|
||||
class Address
|
||||
class Address extends Test
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super('address');
|
||||
this.comparator = 'i;ascii-casemap';
|
||||
this.address_part = ':all'; // :localpart | :domain | :all
|
||||
this.match_type = ':is';
|
||||
this.header_list = new Sieve.Grammar.StringList;
|
||||
this.key_list = new Sieve.Grammar.StringList;
|
||||
this.header_list = new StringList;
|
||||
this.key_list = new StringList;
|
||||
}
|
||||
|
||||
toString()
|
||||
|
|
@ -37,37 +42,50 @@ class Address
|
|||
/**
|
||||
* https://tools.ietf.org/html/rfc5228#section-5.2
|
||||
*/
|
||||
class AllOf extends Sieve.Grammar.TestList
|
||||
class AllOf extends Test
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super('allof');
|
||||
this.tests = new Grammar.TestList;
|
||||
}
|
||||
|
||||
toString()
|
||||
{
|
||||
return 'allof' + this.tests;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* https://tools.ietf.org/html/rfc5228#section-5.3
|
||||
*/
|
||||
class AnyOf extends Sieve.Grammar.TestList
|
||||
class AnyOf extends Test
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super('anyof');
|
||||
this.tests = new Grammar.TestList;
|
||||
}
|
||||
|
||||
toString()
|
||||
{
|
||||
return 'anyof' + this.tests;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* https://tools.ietf.org/html/rfc5228#section-5.4
|
||||
*/
|
||||
class Envelope
|
||||
class Envelope extends Test
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super('envelope');
|
||||
this.comparator = 'i;ascii-casemap';
|
||||
this.address_part = ':all'; // :localpart | :domain | :all
|
||||
this.match_type = ':is';
|
||||
this.envelope_part = new Sieve.Grammar.StringList;
|
||||
this.key_list = new Sieve.Grammar.StringList;
|
||||
this.envelope_part = new StringList;
|
||||
this.key_list = new StringList;
|
||||
}
|
||||
|
||||
toString()
|
||||
|
|
@ -89,16 +107,17 @@ class Envelope
|
|||
/**
|
||||
* https://tools.ietf.org/html/rfc5228#section-5.5
|
||||
*/
|
||||
class Exists
|
||||
class Exists extends Test
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
this.header_names = new Sieve.Grammar.StringList;
|
||||
super('exists');
|
||||
this.header_names = new StringList;
|
||||
}
|
||||
|
||||
toString()
|
||||
{
|
||||
return "exists {this.header_names}";
|
||||
return 'exists ' + this.header_names;
|
||||
}
|
||||
|
||||
pushArguments(/*args*/)
|
||||
|
|
@ -110,7 +129,7 @@ class Exists
|
|||
/**
|
||||
* https://tools.ietf.org/html/rfc5228#section-5.6
|
||||
*/
|
||||
class False
|
||||
class False extends Test
|
||||
{
|
||||
toString()
|
||||
{
|
||||
|
|
@ -121,15 +140,16 @@ class False
|
|||
/**
|
||||
* https://tools.ietf.org/html/rfc5228#section-5.7
|
||||
*/
|
||||
class Header
|
||||
class Header extends Test
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super('header');
|
||||
this.comparator = 'i;ascii-casemap';
|
||||
this.address_part = ':all'; // :localpart | :domain | :all
|
||||
this.match_type = ':is';
|
||||
this.header_names = new Sieve.Grammar.StringList;
|
||||
this.key_list = new Sieve.Grammar.StringList;
|
||||
this.header_names = new StringList;
|
||||
this.key_list = new StringList;
|
||||
}
|
||||
|
||||
toString()
|
||||
|
|
@ -146,7 +166,7 @@ class Header
|
|||
args.forEach((arg, i) => {
|
||||
if (':is' === arg || ':contains' === arg || ':matches' === arg) {
|
||||
this.match_type = arg;
|
||||
} else if (arg instanceof Sieve.Grammar.StringList || arg instanceof Sieve.Grammar.StringType) {
|
||||
} else if (arg instanceof StringList || arg instanceof Grammar.StringType) {
|
||||
this[args[i+1] ? 'header_names' : 'key_list'] = arg;
|
||||
// (args[i+1] ? this.header_names : this.key_list) = arg;
|
||||
}
|
||||
|
|
@ -157,11 +177,12 @@ class Header
|
|||
/**
|
||||
* https://tools.ietf.org/html/rfc5228#section-5.8
|
||||
*/
|
||||
class Not
|
||||
class Not extends Test
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
this.test = new Sieve.Grammar.Test;
|
||||
super('not');
|
||||
this.test = new Test;
|
||||
}
|
||||
|
||||
toString()
|
||||
|
|
@ -178,10 +199,11 @@ class Not
|
|||
/**
|
||||
* https://tools.ietf.org/html/rfc5228#section-5.9
|
||||
*/
|
||||
class Size
|
||||
class Size extends Test
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
super('size');
|
||||
this.mode = ':over'; // :under
|
||||
this.limit = 0;
|
||||
}
|
||||
|
|
@ -196,13 +218,24 @@ class Size
|
|||
args.forEach(arg => {
|
||||
if (':over' === arg || ':under' === arg) {
|
||||
this.mode = arg;
|
||||
} else if (arg instanceof Sieve.Grammar.Number) {
|
||||
} else if (arg instanceof Grammar.Number) {
|
||||
this.limit = arg;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* https://tools.ietf.org/html/rfc5228#section-5.10
|
||||
*/
|
||||
class True extends Test
|
||||
{
|
||||
toString()
|
||||
{
|
||||
return 'true';
|
||||
}
|
||||
}
|
||||
|
||||
Sieve.Tests = {
|
||||
Address: Address,
|
||||
AllOf: AllOf,
|
||||
|
|
@ -212,7 +245,8 @@ Sieve.Tests = {
|
|||
False: False,
|
||||
Header: Header,
|
||||
Not: Not,
|
||||
Size: Size
|
||||
Size: Size,
|
||||
True: True
|
||||
};
|
||||
|
||||
})(this.Sieve);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue