2022-03-09 19:33:31 +08:00
|
|
|
/**
|
|
|
|
* https://tools.ietf.org/html/rfc5228#section-8.2
|
|
|
|
*/
|
|
|
|
|
|
|
|
import {
|
|
|
|
QUOTED_TEXT,
|
|
|
|
HASH_COMMENT,
|
|
|
|
MULTILINE_LITERAL,
|
|
|
|
MULTILINE_DOTSTART
|
|
|
|
} from 'Sieve/RegEx';
|
|
|
|
|
2022-03-16 21:33:43 +08:00
|
|
|
import { arrayToString, getMatchTypes } from 'Sieve/Utils';
|
2022-03-09 19:33:31 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* abstract
|
|
|
|
*/
|
|
|
|
export class GrammarString /*extends String*/
|
|
|
|
{
|
|
|
|
constructor(value = '')
|
|
|
|
{
|
|
|
|
this._value = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
toString()
|
|
|
|
{
|
|
|
|
return this._value;
|
|
|
|
}
|
|
|
|
|
|
|
|
get value()
|
|
|
|
{
|
|
|
|
return this._value;
|
|
|
|
}
|
|
|
|
|
|
|
|
set value(value)
|
|
|
|
{
|
|
|
|
this._value = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
get length()
|
|
|
|
{
|
|
|
|
return this._value.length;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* abstract
|
|
|
|
*/
|
|
|
|
export class GrammarComment extends GrammarString
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* https://tools.ietf.org/html/rfc5228#section-2.9
|
|
|
|
*/
|
|
|
|
export class GrammarCommand
|
|
|
|
{
|
|
|
|
constructor(identifier)
|
|
|
|
{
|
|
|
|
this.identifier = identifier || this.constructor.name.toLowerCase().replace('command', '');
|
|
|
|
this.arguments = [];
|
|
|
|
this.commands = new GrammarCommands;
|
|
|
|
}
|
|
|
|
|
|
|
|
toString()
|
|
|
|
{
|
|
|
|
let result = this.identifier;
|
|
|
|
if (this.arguments.length) {
|
|
|
|
result += ' ' + arrayToString(this.arguments, ' ');
|
|
|
|
}
|
|
|
|
return result + (
|
2022-03-16 21:21:23 +08:00
|
|
|
this.commands.length ? ' ' + this.commands : ';'
|
2022-03-09 19:33:31 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
getComparators()
|
|
|
|
{
|
|
|
|
return ['i;ascii-casemap'];
|
|
|
|
}
|
|
|
|
|
|
|
|
getMatchTypes()
|
|
|
|
{
|
|
|
|
return [':is', ':contains', ':matches'];
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 (1 < this.length) {
|
|
|
|
return '[' + this.join(',') + ']';
|
|
|
|
}
|
|
|
|
return this.length ? this[0] : '';
|
|
|
|
}
|
|
|
|
|
|
|
|
push(value)
|
|
|
|
{
|
|
|
|
if (!(value instanceof GrammarString)) {
|
|
|
|
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
|
|
|
|
{
|
|
|
|
toString()
|
|
|
|
{
|
|
|
|
return '"' + this._value.replace(/[\\"]/g, '\\$&') + '"';
|
|
|
|
// return '"' + super.toString().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();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* https://tools.ietf.org/html/rfc5228#section-5
|
|
|
|
*/
|
|
|
|
export class GrammarTest
|
|
|
|
{
|
|
|
|
constructor(identifier)
|
|
|
|
{
|
2022-03-14 19:39:11 +08:00
|
|
|
this.identifier = identifier || this.constructor.name.toLowerCase().replace(/test$/, '');
|
2022-03-09 19:33:31 +08:00
|
|
|
// Almost every test has a comparator and match_type, so define them here
|
|
|
|
this.comparator = '',
|
|
|
|
this.match_type = ':is',
|
|
|
|
this.arguments = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
toString()
|
|
|
|
{
|
2022-03-16 21:21:23 +08:00
|
|
|
// https://datatracker.ietf.org/doc/html/rfc6134#section-2.3
|
2022-03-16 21:33:43 +08:00
|
|
|
if (!getMatchTypes().includes(this.match_type)) {
|
2022-03-16 21:21:23 +08:00
|
|
|
throw 'Unsupported match-type ' + this.match_type;
|
|
|
|
}
|
2022-03-09 19:33:31 +08:00
|
|
|
return (this.identifier
|
|
|
|
+ (this.comparator ? ' :comparator ' + this.comparator : '')
|
|
|
|
+ (this.match_type ? ' ' + this.match_type : '')
|
|
|
|
+ ' ' + arrayToString(this.arguments, ' ')).trim();
|
|
|
|
}
|
|
|
|
|
|
|
|
pushArguments(args)
|
|
|
|
{
|
|
|
|
this.arguments = args;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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] : '';
|
|
|
|
}
|
|
|
|
|
|
|
|
push(value)
|
|
|
|
{
|
|
|
|
if (!(value instanceof GrammarTest)) {
|
|
|
|
throw 'Not an instanceof Test';
|
|
|
|
}
|
|
|
|
super.push(value);
|
|
|
|
}
|
|
|
|
}
|