snappymail/dev/Sieve/Grammar.js

380 lines
7.1 KiB
JavaScript

/**
* https://tools.ietf.org/html/rfc5228#section-8.2
*/
import {
QUOTED_TEXT,
HASH_COMMENT,
MULTILINE_LITERAL,
MULTILINE_DOTSTART
} from 'Sieve/RegEx';
import { arrayToString, getMatchTypes, getComparators } from 'Sieve/Utils';
/**
* abstract
*/
export class GrammarString /*extends String*/
{
constructor(value = '')
{
this._value = value.toString ? value.toString() : value;
}
toString()
{
return this._value;
}
get value()
{
return this._value;
}
set value(value)
{
this._value = value;
}
get length()
{
return this._value.length;
}
}
/**
* abstract
*/
export /*abstract*/ class GrammarComment extends GrammarString
{
/*
constructor()
{
if (this.constructor == GrammarComment) {
throw Error("Abstract class can't be instantiated.");
}
}
*/
}
/**
* https://tools.ietf.org/html/rfc5228#section-2.9
*/
const cmdNameSuffix = /(test|command|action)$/;
export /*abstract*/ class GrammarCommand
{
constructor(identifier)
{
/*
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) {
result += ' ' + arrayToString(this.arguments, ' ');
}
return result + ';';
}
pushArguments(args)
{
this.arguments = args;
}
}
export class GrammarCommands extends Array
{
toString()
{
return this.length
? '{\r\n\t' + arrayToString(this, '\r\n\t') + '\r\n}'
: '{}';
}
push(value)
{
if (value instanceof GrammarCommand || value instanceof GrammarComment) {
super.push(value);
}
}
}
/**
* https://tools.ietf.org/html/rfc5228#section-3
*/
export /*abstract*/ class ControlCommand extends GrammarCommand
{
/*
constructor(identifier)
{
if (this.constructor == ControlCommand) {
throw Error("Abstract class can't be instantiated.");
}
super(identifier);
}
*/
}
/**
* https://tools.ietf.org/html/rfc5228#section-4
*/
export /*abstract*/ class ActionCommand extends GrammarCommand
{
/*
constructor(identifier)
{
if (this.constructor == ActionCommand) {
throw Error("Abstract class can't be instantiated.");
}
super(identifier);
}
*/
}
/**
* https://tools.ietf.org/html/rfc5228#section-5
*/
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 = '';
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()
{
return (this.identifier
+ (this._comparator ? ' :comparator ' + this._comparator : '')
+ (this._match_type ? ' ' + this._match_type : '')
+ (this.relational_match ? ' ' + this.relational_match : '')
+ ' ' + arrayToString(this.arguments, ' ')).trim();
}
}
/**
* https://tools.ietf.org/html/rfc5228#section-5.2
* https://tools.ietf.org/html/rfc5228#section-5.3
*/
export class GrammarTestList extends Array
{
toString()
{
if (1 < this.length) {
// return '(\r\n\t' + arrayToString(this, ',\r\n\t') + '\r\n)';
return '(' + this.join(', ') + ')';
}
return this.length ? this[0].toString() : '';
}
push(value)
{
if (!(value instanceof TestCommand)) {
throw 'Not an instanceof Test';
}
super.push(value);
}
}
export class GrammarBracketComment extends GrammarComment
{
toString()
{
return '/* ' + super.toString() + ' */';
}
}
export class GrammarHashComment extends GrammarComment
{
toString()
{
return '# ' + super.toString();
}
}
export class GrammarNumber /*extends Number*/
{
constructor(value = '0')
{
this._value = value;
}
toString()
{
return this._value;
}
get value()
{
return this._value;
}
set value(value)
{
this._value = value;
}
}
export class GrammarStringList extends Array
{
toString()
{
// if there is only a single string, the brackets are optional
if (1 < this.length) {
return '[' + this.join(',') + ']';
}
return this.length ? this[0].toString() : '';
}
push(value)
{
if (!(value instanceof GrammarQuotedString)) {
value = new GrammarQuotedString(value);
}
super.push(value);
}
}
const StringListRegEx = RegExp('(?:^\\s*|\\s*,\\s*)(?:"(' + QUOTED_TEXT + ')"|text:[ \\t]*('
+ HASH_COMMENT + ')?\\r\\n'
+ '((?:' + MULTILINE_LITERAL + '|' + MULTILINE_DOTSTART + ')*)'
+ '\\.\\r\\n)', 'gm');
GrammarStringList.fromString = list => {
let string,
obj = new GrammarStringList;
list = list.replace(/^[\r\n\t[]+/, '');
while ((string = StringListRegEx.exec(list))) {
if (string[3]) {
obj.push(new GrammarMultiLine(string[3], string[2]));
} else {
obj.push(new GrammarQuotedString(string[1]));
}
}
return obj;
}
export class GrammarQuotedString extends GrammarString
{
constructor(value = '')
{
super(value instanceof GrammarQuotedString ? value.value : value);
}
toString()
{
return '"' + this._value.replace(/[\\"]/g, '\\$&') + '"';
}
}
/**
* https://tools.ietf.org/html/rfc5228#section-8.1
*/
export class GrammarMultiLine extends GrammarString
{
constructor(value, comment = '')
{
super();
this.value = value;
this.comment = comment;
}
toString()
{
return 'text:'
+ (this.comment ? '# ' + this.comment : '') + "\r\n"
+ this.value
+ "\r\n.\r\n";
}
}
const MultiLineRegEx = RegExp('text:[ \\t]*(' + HASH_COMMENT + ')?\\r\\n'
+ '((?:' + MULTILINE_LITERAL + '|' + MULTILINE_DOTSTART + ')*)'
+ '\\.\\r\\n', 'm');
GrammarMultiLine.fromString = string => {
string = string.match(MultiLineRegEx);
if (string[2]) {
return new GrammarMultiLine(string[2].replace(/\r\n$/, ''), string[1]);
}
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 : ';'
);
}
}