Simplify Sieve Parser and added RFC5235

This commit is contained in:
djmaze 2021-01-14 23:42:46 +01:00
parent 22964f1fde
commit d9865e3a46
11 changed files with 308 additions and 92 deletions

View file

@ -23,9 +23,7 @@ class Conditional extends Command
toString()
{
return this.identifier
+ ('else' !== this.identifier ? ' ' + this.test : '')
+ ' ' + this.commands;
return this.identifier + ' ' + this.test + ' ' + this.commands;
}
/*
public function pushArguments(array $args): void
@ -36,6 +34,35 @@ class Conditional extends Command
*/
}
class If extends Conditional
{
constructor()
{
super('if');
}
}
class ElsIf extends Conditional
{
constructor()
{
super('elsif');
}
}
class Else extends Conditional
{
constructor()
{
super('else');
}
toString()
{
return this.identifier + ' ' + this.commands;
}
}
/**
* https://tools.ietf.org/html/rfc5228#section-3.2
*/
@ -83,13 +110,12 @@ class Stop extends Command
*/
class FileInto extends Command
{
// const REQUIRE = 'fileinto';
constructor()
{
super('fileinto');
// QuotedString / MultiLine
this._mailbox = new Grammar.QuotedString();
// this.require = 'fileinto';
}
toString()
@ -177,14 +203,17 @@ class Discard extends Command
Sieve.Commands = {
// Control commands
Conditional: Conditional,
Require: Require,
Stop: Stop,
if: If,
elsif: ElsIf,
else: Else,
conditional: Conditional,
require: Require,
stop: Stop,
// Action commands
Discard: Discard,
Fileinto: FileInto,
Keep: Keep,
Redirect: Redirect
discard: Discard,
fileinto: FileInto,
keep: Keep,
redirect: Redirect
};
})(this.Sieve);

View file

@ -1,17 +0,0 @@
/**
* https://tools.ietf.org/html/rfc5232
*/
(Sieve => {
Sieve.Extensions.Imap4flags = class
{
/**
* setflag [<variablename: string>] <list-of-flags: string-list>
* addflag [<variablename: string>] <list-of-flags: string-list>
* removeflag [<variablename: string>] <list-of-flags: string-list>
* hasflag [MATCH-TYPE] [COMPARATOR] [<variable-list: string-list>] <list-of-flags: string-list>
*/
};
})(this.Sieve);

View file

@ -15,6 +15,7 @@ class Body extends Grammar.Test
this.match_type = ':is',
this.body_transform = ''; // :raw, :content <string-list>, :text
this.key_list = new Grammar.StringList;
// this.require = 'body';
}
toString()
@ -26,7 +27,6 @@ class Body extends Grammar.Test
+ ' ' + this.key_list;
}
pushArguments(args)
{
args.forEach((arg, i) => {
@ -43,7 +43,7 @@ class Body extends Grammar.Test
}
}
Sieve.Extensions.Body = Body;
Sieve.Commands.body = Body;
})(this.Sieve);

View file

@ -4,20 +4,23 @@
(Sieve => {
class Vacation extends Sieve.Grammar.Command
const Grammar = Sieve.Grammar;
class Vacation extends Grammar.Command
{
constructor()
{
super('vacation');
this.arguments = {
':days' : new Sieve.Grammar.Number,
':subject' : new Sieve.Grammar.QuotedString,
':from' : new Sieve.Grammar.QuotedString,
':addresses': new Sieve.Grammar.StringList,
':days' : new Grammar.Number,
':subject' : new Grammar.QuotedString,
':from' : new Grammar.QuotedString,
':addresses': new Grammar.StringList,
':mime' : false,
':handle' : new Sieve.Grammar.QuotedString
':handle' : new Grammar.QuotedString
};
this.reason = ''; // QuotedString / MultiLine
// this.require = 'vacation';
}
toString()
@ -82,6 +85,6 @@ class Vacation extends Sieve.Grammar.Command
*/
}
Sieve.Extensions.Vacation = Vacation;
Sieve.Commands.vacation = Vacation;
})(this.Sieve);

View file

@ -0,0 +1,114 @@
/**
* https://tools.ietf.org/html/rfc5232
*/
(Sieve => {
const Grammar = Sieve.Grammar;
class Flag extends Grammar.Command
{
constructor(identifier)
{
super(identifier);
this._variablename = new Grammar.QuotedString;
this.list_of_flags = new Grammar.StringList;
// this.require = 'imap4flags';
}
toString()
{
return this.identifier + ' ' + this._variablename + ' ' + this.list_of_flags + ';';
}
get variablename()
{
return this._variablename.value;
}
set variablename(value)
{
this._variablename.value = value;
}
pushArguments(args)
{
if (args[1]) {
if (args[0] instanceof Grammar.QuotedString) {
this._variablename = args[0];
}
if (args[1] instanceof Grammar.StringType) {
this.list_of_flags = args[1];
}
} else if (args[0] instanceof Grammar.StringType) {
this.list_of_flags = args[0];
}
}
}
class SetFlag extends Flag
{
constructor()
{
super('setflag');
}
}
class AddFlag extends Flag
{
constructor()
{
super('addflag');
}
}
class RemoveFlag extends Flag
{
constructor()
{
super('removeflag');
}
}
class HasFlag extends Grammar.Test
{
constructor()
{
super('hasflag');
this.comparator = 'i;ascii-casemap',
this.match_type = ':is',
this.variable_list = new Grammar.StringList;
this.list_of_flags = new Grammar.StringList;
// this.require = 'imap4flags';
}
toString()
{
return 'hasflag'
+ ' ' + this.match_type
// + ' ' + this.comparator
+ ' ' + this.variable_list
+ ' ' + this.list_of_flags;
}
pushArguments(args)
{
args.forEach((arg, i) => {
if (':is' === arg || ':contains' === arg || ':matches' === arg) {
this.match_type = arg;
} else if (arg instanceof Grammar.StringList || arg instanceof Grammar.StringType) {
this[args[i+1] ? 'variable_list' : 'list_of_flags'] = arg;
}
});
}
}
Object.assign(Sieve.Commands, {
setflag: SetFlag,
addflag: AddFlag,
removeflag: RemoveFlag,
hasflag: HasFlag
// mark and unmark never made it into the RFC
});
})(this.Sieve);

View file

@ -0,0 +1,95 @@
/**
* https://tools.ietf.org/html/rfc5235
*/
(Sieve => {
const Grammar = Sieve.Grammar;
class SpamTest extends Grammar.Test
{
constructor()
{
super('spamtest');
this.percent = false, // 0 - 100 else 0 - 10
this.comparator = 'i;ascii-casemap',
this.match_type = ':is',
this.value = new Grammar.QuotedString;
// this.require = this.percent ? 'spamtestplus' : 'spamtest';
}
toString()
{
return 'spamtest'
+ (this.percent ? ' :percent' : '')
// + ' ' + this.comparator
+ ' ' + this.match_type
+ ' ' + this.value;
}
pushArguments(args)
{
args.forEach((arg, i) => {
if (':is' === arg || ':contains' === arg || ':matches' === arg) {
this.match_type = arg;
} else if (':percent' === arg) {
this.percent = true;
} else if (arg instanceof Grammar.StringType) {
if (':comparator' === args[i-1]) {
this.comparator = arg;
} else if (':value' === args[i-1] || ':count' === args[i-1]) {
// Sieve relational [RFC5231] match types
this.match_type = args[i-1] + ' ' + arg;
} else {
this.value = arg;
}
}
});
}
}
class VirusTest extends Grammar.Test
{
constructor()
{
super('virustest');
this.comparator = 'i;ascii-casemap',
this.match_type = ':is',
this.value = new Grammar.QuotedString; // 1 - 5
// this.require = 'virustest';
}
toString()
{
return 'virustest'
// + ' ' + this.comparator
+ ' ' + this.match_type
+ ' ' + this.value;
}
pushArguments(args)
{
args.forEach((arg, i) => {
if (':is' === arg || ':contains' === arg || ':matches' === arg) {
this.match_type = arg;
} else if (arg instanceof Grammar.StringType) {
if (':comparator' === args[i-1]) {
this.comparator = arg;
} else if (':value' === args[i-1] || ':count' === args[i-1]) {
// Sieve relational [RFC5231] match types
this.match_type = args[i-1] + ' ' + arg;
} else {
this.value = arg;
}
}
});
}
}
Object.assign(Sieve.Commands, {
spamtest: SpamTest,
virustest: VirusTest
});
})(this.Sieve);

View file

@ -15,6 +15,7 @@ class Ereject extends Grammar.Command
{
super('ereject');
this._reason = new Grammar.QuotedString;
// this.require = 'ereject';
}
toString()
@ -49,6 +50,7 @@ class Reject extends Grammar.Command
{
super('reject');
this._reason = new Grammar.QuotedString;
// this.require = 'reject';
}
toString()
@ -74,7 +76,7 @@ class Reject extends Grammar.Command
}
}
Sieve.Extensions.Ereject = Ereject;
Sieve.Extensions.Reject = Reject;
Sieve.Commands.ereject = Ereject;
Sieve.Commands.reject = Reject;
})(this.Sieve);

View file

@ -6,6 +6,8 @@
const
RegEx = Sieve.RegEx,
Grammar = Sieve.Grammar,
Commands = Sieve.Commands,
T_UNKNOWN = 0,
T_STRING_LIST = 1,
@ -80,43 +82,35 @@ Sieve.parseScript = script => {
case T_IDENTIFIER: {
pushArgs();
value = value.toLowerCase();
let new_command,
className = value[0].toUpperCase() + value.substring(1);
let new_command;
if ('if' === value) {
new_command = new Sieve.Commands.Conditional(value);
new_command = new Commands.conditional(value);
} else if ('elsif' === value || 'else' === value) {
// (prev_command instanceof Sieve.Commands.Conditional) || error('Not after IF condition');
new_command = new Sieve.Commands.Conditional(value);
} else if ('allof' === value || 'anyof' === value) {
(command instanceof Sieve.Commands.Conditional) || error('Test-list not in conditional');
new_command = new Sieve.Tests[className]();
} else if (Sieve.Tests[className]) {
// address / envelope / exists / header / not / size
new_command = new Sieve.Tests[className]();
} else if (Sieve.Commands[className]) {
// discard / fileinto / keep / redirect / require / stop
new_command = new Sieve.Commands[className]();
} else if (Sieve.Extensions[className]) {
// body / ereject / reject / imap4flags / vacation
new_command = new Sieve.Extensions[className]();
// (prev_command instanceof Commands.conditional) || error('Not after IF condition');
new_command = new Commands.conditional(value);
} else if (Commands[value]) {
if ('allof' === value || 'anyof' === value) {
(command instanceof Commands.conditional) || error('Test-list not in conditional');
}
new_command = new Commands[value]();
} else {
console.error('Unknown command: ' + value);
if (command && (
command instanceof Sieve.Commands.Conditional
|| command instanceof Sieve.Tests.Not
|| command.tests instanceof Sieve.Grammar.TestList)) {
new_command = new Sieve.Grammar.Test(value);
command instanceof Commands.conditional
|| command instanceof Commands.not
|| command.tests instanceof Grammar.TestList)) {
new_command = new Grammar.Test(value);
} else {
new_command = new Sieve.Grammar.Command(value);
new_command = new Grammar.Command(value);
}
}
if (new_command instanceof Sieve.Grammar.Test) {
if (command instanceof Sieve.Commands.Conditional || command instanceof Sieve.Tests.Not) {
if (new_command instanceof Grammar.Test) {
if (command instanceof Commands.conditional || command instanceof Commands.not) {
// if/elsif/else new_command
// not new_command
command.test = new_command;
} else if (command.tests instanceof Sieve.Grammar.TestList) {
} else if (command.tests instanceof Grammar.TestList) {
// allof/anyof .tests[] new_command
command.tests.push(new_command);
} else {
@ -144,35 +138,35 @@ Sieve.parseScript = script => {
break;
case T_STRING_LIST:
command
? args.push(Sieve.Grammar.StringList.fromString(value))
? args.push(Grammar.StringList.fromString(value))
: error('String list must be command argument');
break;
case T_MULTILINE_STRING:
command
? args.push(new Sieve.Grammar.MultiLine(value))
? args.push(new Grammar.MultiLine(value))
: error('Multi-line string must be command argument');
break;
case T_QUOTED_STRING:
command
? args.push(new Sieve.Grammar.QuotedString(value.substr(1,value.length-2)))
? args.push(new Grammar.QuotedString(value.substr(1,value.length-2)))
: error('Quoted string must be command argument');
break;
case T_NUMBER:
command
? args.push(new Sieve.Grammar.Number(value))
? args.push(new Grammar.Number(value))
: 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))
new Grammar.BracketComment(value.substr(2, value.length-4))
);
break;
case T_HASH_COMMENT:
(command ? command.commands : tree).push(
new Sieve.Grammar.HashComment(value.substr(1).trim())
new Grammar.HashComment(value.substr(1).trim())
);
break;
@ -194,14 +188,14 @@ Sieve.parseScript = script => {
pushArgs();
// https://tools.ietf.org/html/rfc5228#section-2.9
// Action commands do not take tests or blocks
while (command && !(command instanceof Sieve.Commands.Conditional)) {
while (command && !(command instanceof Commands.conditional)) {
levels.pop();
command = levels.last();
}
command || error('Block start not part of control command');
break;
case T_BLOCK_END:
(command instanceof Sieve.Commands.Conditional) || error('Block end has no matching block start');
(command instanceof Commands.conditional) || error('Block end has no matching block start');
levels.pop();
// prev_command = command;
command = levels.last();
@ -210,7 +204,7 @@ Sieve.parseScript = script => {
// anyof / allof ( ... , ... )
case T_LEFT_PARENTHESIS:
pushArgs();
while (command && !(command.tests instanceof Sieve.Grammar.TestList)) {
while (command && !(command.tests instanceof Grammar.TestList)) {
levels.pop();
command = levels.last();
}
@ -220,14 +214,14 @@ Sieve.parseScript = script => {
pushArgs();
levels.pop();
command = levels.last();
(command.tests instanceof Sieve.Grammar.TestList) || error('Test end not part of test-list');
(command.tests instanceof Grammar.TestList) || error('Test end not part of test-list');
break;
case T_COMMA:
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');
(command.tests instanceof Grammar.TestList) || error('Comma not part of test-list');
break;
case T_UNKNOWN:

View file

@ -236,17 +236,17 @@ class True extends Test
}
}
Sieve.Tests = {
Address: Address,
Allof: AllOf,
Anyof: AnyOf,
Envelope: Envelope,
Exists: Exists,
False: False,
Header: Header,
Not: Not,
Size: Size,
True: True
};
Object.assign(Sieve.Commands, {
address: Address,
allof: AllOf,
anyof: AnyOf,
envelope: Envelope,
exists: Exists,
false: False,
header: Header,
not: Not,
size: Size,
true: True
});
})(this.Sieve);

View file

@ -6,11 +6,8 @@
RegEx: {},
Grammar: {},
Commands: {},
Tests: {},
parseScript: ()=>{},
*/
Extensions: {},
arrayToString: (arr, separator) =>
arr.map(item => item.toString ? item.toString() : item).join(separator)
};

View file

@ -57,7 +57,6 @@ const jsSieve = () => {
.pipe(expect.real({ errorOnFailure: true }, src))
.pipe(concat(config.paths.js.sieve.name, { separator: '\n\n' }))
.pipe(eol('\n', true))
.pipe(replace(/sourceMappingURL=[a-z0-9.\-_]{1,20}\.map/gi, ''))
.pipe(gulp.dest(config.paths.staticJS));
};