[Web] Autodiscover returns given password decoded and trimed; Add sieve pre and post filters to UI; Move ajax called files; Rework log system: 100 entries per default, add more per click; Syncjobs: Do not read log to data attribute
This commit is contained in:
7
data/web/inc/lib/sieve/SieveDumpable.php
Normal file
7
data/web/inc/lib/sieve/SieveDumpable.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php namespace Sieve;
|
||||
|
||||
interface SieveDumpable
|
||||
{
|
||||
function dump();
|
||||
function text();
|
||||
}
|
47
data/web/inc/lib/sieve/SieveException.php
Normal file
47
data/web/inc/lib/sieve/SieveException.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php namespace Sieve;
|
||||
|
||||
require_once('SieveToken.php');
|
||||
|
||||
use Exception;
|
||||
|
||||
class SieveException extends Exception
|
||||
{
|
||||
protected $token_;
|
||||
|
||||
public function __construct(SieveToken $token, $arg)
|
||||
{
|
||||
$message = 'undefined sieve exception';
|
||||
$this->token_ = $token;
|
||||
|
||||
if (is_string($arg))
|
||||
{
|
||||
$message = $arg;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (is_array($arg))
|
||||
{
|
||||
$type = SieveToken::typeString(array_shift($arg));
|
||||
foreach($arg as $t)
|
||||
{
|
||||
$type .= ' or '. SieveToken::typeString($t);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$type = SieveToken::typeString($arg);
|
||||
}
|
||||
|
||||
$tokenType = SieveToken::typeString($token->type);
|
||||
$message = "$tokenType where $type expected near ". $token->text;
|
||||
}
|
||||
|
||||
parent::__construct('line '. $token->line .": $message");
|
||||
}
|
||||
|
||||
public function getLineNo()
|
||||
{
|
||||
return $this->token_->line;
|
||||
}
|
||||
|
||||
}
|
233
data/web/inc/lib/sieve/SieveKeywordRegistry.php
Normal file
233
data/web/inc/lib/sieve/SieveKeywordRegistry.php
Normal file
@@ -0,0 +1,233 @@
|
||||
<?php namespace Sieve;
|
||||
|
||||
class SieveKeywordRegistry
|
||||
{
|
||||
protected $registry_ = array();
|
||||
protected $matchTypes_ = array();
|
||||
protected $comparators_ = array();
|
||||
protected $addressParts_ = array();
|
||||
protected $commands_ = array();
|
||||
protected $tests_ = array();
|
||||
protected $arguments_ = array();
|
||||
|
||||
protected static $refcount = 0;
|
||||
protected static $instance = null;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
$keywords = simplexml_load_file(dirname(__FILE__) .'/keywords.xml');
|
||||
foreach ($keywords->children() as $keyword)
|
||||
{
|
||||
switch ($keyword->getName())
|
||||
{
|
||||
case 'matchtype':
|
||||
$type =& $this->matchTypes_;
|
||||
break;
|
||||
case 'comparator':
|
||||
$type =& $this->comparators_;
|
||||
break;
|
||||
case 'addresspart':
|
||||
$type =& $this->addressParts_;
|
||||
break;
|
||||
case 'test':
|
||||
$type =& $this->tests_;
|
||||
break;
|
||||
case 'command':
|
||||
$type =& $this->commands_;
|
||||
break;
|
||||
default:
|
||||
trigger_error('Unsupported keyword type "'. $keyword->getName()
|
||||
. '" in file "keywords/'. basename($file) .'"');
|
||||
return;
|
||||
}
|
||||
|
||||
$name = (string) $keyword['name'];
|
||||
if (array_key_exists($name, $type))
|
||||
trigger_error("redefinition of $type $name - skipping");
|
||||
else
|
||||
$type[$name] = $keyword->children();
|
||||
}
|
||||
|
||||
foreach (glob(dirname(__FILE__) .'/extensions/*.xml') as $file)
|
||||
{
|
||||
$extension = simplexml_load_file($file);
|
||||
$name = (string) $extension['name'];
|
||||
|
||||
if (array_key_exists($name, $this->registry_))
|
||||
{
|
||||
trigger_error('overwriting extension "'. $name .'"');
|
||||
}
|
||||
$this->registry_[$name] = $extension;
|
||||
}
|
||||
}
|
||||
|
||||
public static function get()
|
||||
{
|
||||
if (self::$instance == null)
|
||||
{
|
||||
self::$instance = new SieveKeywordRegistry();
|
||||
}
|
||||
|
||||
self::$refcount++;
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function put()
|
||||
{
|
||||
if (--self::$refcount == 0)
|
||||
{
|
||||
self::$instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
public function activate($extension)
|
||||
{
|
||||
if (!isset($this->registry_[$extension]))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$xml = $this->registry_[$extension];
|
||||
|
||||
foreach ($xml->children() as $e)
|
||||
{
|
||||
switch ($e->getName())
|
||||
{
|
||||
case 'matchtype':
|
||||
$type =& $this->matchTypes_;
|
||||
break;
|
||||
case 'comparator':
|
||||
$type =& $this->comparators_;
|
||||
break;
|
||||
case 'addresspart':
|
||||
$type =& $this->addressParts_;
|
||||
break;
|
||||
case 'test':
|
||||
$type =& $this->tests_;
|
||||
break;
|
||||
case 'command':
|
||||
$type =& $this->commands_;
|
||||
break;
|
||||
case 'tagged-argument':
|
||||
$xml = $e->parameter[0];
|
||||
$this->arguments_[(string) $xml['name']] = array(
|
||||
'extends' => (string) $e['extends'],
|
||||
'rules' => $xml
|
||||
);
|
||||
continue;
|
||||
default:
|
||||
trigger_error('Unsupported extension type \''.
|
||||
$e->getName() ."' in extension '$extension'");
|
||||
return;
|
||||
}
|
||||
|
||||
$name = (string) $e['name'];
|
||||
if (!isset($type[$name]) ||
|
||||
(string) $e['overrides'] == 'true')
|
||||
{
|
||||
$type[$name] = $e->children();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function isTest($name)
|
||||
{
|
||||
return (isset($this->tests_[$name]) ? true : false);
|
||||
}
|
||||
|
||||
public function isCommand($name)
|
||||
{
|
||||
return (isset($this->commands_[$name]) ? true : false);
|
||||
}
|
||||
|
||||
public function matchtype($name)
|
||||
{
|
||||
if (isset($this->matchTypes_[$name]))
|
||||
{
|
||||
return $this->matchTypes_[$name];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function addresspart($name)
|
||||
{
|
||||
if (isset($this->addressParts_[$name]))
|
||||
{
|
||||
return $this->addressParts_[$name];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function comparator($name)
|
||||
{
|
||||
if (isset($this->comparators_[$name]))
|
||||
{
|
||||
return $this->comparators_[$name];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function test($name)
|
||||
{
|
||||
if (isset($this->tests_[$name]))
|
||||
{
|
||||
return $this->tests_[$name];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function command($name)
|
||||
{
|
||||
if (isset($this->commands_[$name]))
|
||||
{
|
||||
return $this->commands_[$name];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function arguments($command)
|
||||
{
|
||||
$res = array();
|
||||
foreach ($this->arguments_ as $arg)
|
||||
{
|
||||
if (preg_match('/'.$arg['extends'].'/', $command))
|
||||
array_push($res, $arg['rules']);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function argument($name)
|
||||
{
|
||||
if (isset($this->arguments_[$name]))
|
||||
{
|
||||
return $this->arguments_[$name]['rules'];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function requireStrings()
|
||||
{
|
||||
return array_keys($this->registry_);
|
||||
}
|
||||
public function matchTypes()
|
||||
{
|
||||
return array_keys($this->matchTypes_);
|
||||
}
|
||||
public function comparators()
|
||||
{
|
||||
return array_keys($this->comparators_);
|
||||
}
|
||||
public function addressParts()
|
||||
{
|
||||
return array_keys($this->addressParts_);
|
||||
}
|
||||
public function tests()
|
||||
{
|
||||
return array_keys($this->tests_);
|
||||
}
|
||||
public function commands()
|
||||
{
|
||||
return array_keys($this->commands_);
|
||||
}
|
||||
}
|
255
data/web/inc/lib/sieve/SieveParser.php
Normal file
255
data/web/inc/lib/sieve/SieveParser.php
Normal file
@@ -0,0 +1,255 @@
|
||||
<?php namespace Sieve;
|
||||
|
||||
include_once 'SieveTree.php';
|
||||
include_once 'SieveScanner.php';
|
||||
include_once 'SieveSemantics.php';
|
||||
include_once 'SieveException.php';
|
||||
|
||||
class SieveParser
|
||||
{
|
||||
protected $scanner_;
|
||||
protected $script_;
|
||||
protected $tree_;
|
||||
protected $status_;
|
||||
|
||||
public function __construct($script = null)
|
||||
{
|
||||
if (isset($script))
|
||||
$this->parse($script);
|
||||
}
|
||||
|
||||
public function GetParseTree()
|
||||
{
|
||||
return $this->tree_;
|
||||
}
|
||||
|
||||
public function dumpParseTree()
|
||||
{
|
||||
return $this->tree_->dump();
|
||||
}
|
||||
|
||||
public function getScriptText()
|
||||
{
|
||||
return $this->tree_->getText();
|
||||
}
|
||||
|
||||
protected function getPrevToken_($parent_id)
|
||||
{
|
||||
$childs = $this->tree_->getChilds($parent_id);
|
||||
|
||||
for ($i = count($childs); $i > 0; --$i)
|
||||
{
|
||||
$prev = $this->tree_->getNode($childs[$i-1]);
|
||||
if ($prev->is(SieveToken::Comment|SieveToken::Whitespace))
|
||||
continue;
|
||||
|
||||
// use command owning a block or list instead of previous
|
||||
if ($prev->is(SieveToken::BlockStart|SieveToken::Comma|SieveToken::LeftParenthesis))
|
||||
$prev = $this->tree_->getNode($parent_id);
|
||||
|
||||
return $prev;
|
||||
}
|
||||
|
||||
return $this->tree_->getNode($parent_id);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* methods for recursive descent start below
|
||||
*/
|
||||
public function passthroughWhitespaceComment($token)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function passthroughFunction($token)
|
||||
{
|
||||
$this->tree_->addChild($token);
|
||||
}
|
||||
|
||||
public function parse($script)
|
||||
{
|
||||
$this->script_ = $script;
|
||||
|
||||
$this->scanner_ = new SieveScanner($this->script_);
|
||||
|
||||
// Define what happens with passthrough tokens like whitespacs and comments
|
||||
$this->scanner_->setPassthroughFunc(
|
||||
array(
|
||||
$this, 'passthroughWhitespaceComment'
|
||||
)
|
||||
);
|
||||
|
||||
$this->tree_ = new SieveTree('tree');
|
||||
|
||||
$this->commands_($this->tree_->getRoot());
|
||||
|
||||
if (!$this->scanner_->nextTokenIs(SieveToken::ScriptEnd)) {
|
||||
$token = $this->scanner_->nextToken();
|
||||
throw new SieveException($token, SieveToken::ScriptEnd);
|
||||
}
|
||||
}
|
||||
|
||||
protected function commands_($parent_id)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (!$this->scanner_->nextTokenIs(SieveToken::Identifier))
|
||||
break;
|
||||
|
||||
// Get and check a command token
|
||||
$token = $this->scanner_->nextToken();
|
||||
$semantics = new SieveSemantics($token, $this->getPrevToken_($parent_id));
|
||||
|
||||
// Process eventual arguments
|
||||
$this_node = $this->tree_->addChildTo($parent_id, $token);
|
||||
$this->arguments_($this_node, $semantics);
|
||||
|
||||
$token = $this->scanner_->nextToken();
|
||||
if (!$token->is(SieveToken::Semicolon))
|
||||
{
|
||||
// TODO: check if/when semcheck is needed here
|
||||
$semantics->validateToken($token);
|
||||
|
||||
if ($token->is(SieveToken::BlockStart))
|
||||
{
|
||||
$this->tree_->addChildTo($this_node, $token);
|
||||
$this->block_($this_node, $semantics);
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new SieveException($token, SieveToken::Semicolon);
|
||||
}
|
||||
|
||||
$semantics->done($token);
|
||||
$this->tree_->addChildTo($this_node, $token);
|
||||
}
|
||||
}
|
||||
|
||||
protected function arguments_($parent_id, &$semantics)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if ($this->scanner_->nextTokenIs(SieveToken::Number|SieveToken::Tag))
|
||||
{
|
||||
// Check if semantics allow a number or tag
|
||||
$token = $this->scanner_->nextToken();
|
||||
$semantics->validateToken($token);
|
||||
$this->tree_->addChildTo($parent_id, $token);
|
||||
}
|
||||
else if ($this->scanner_->nextTokenIs(SieveToken::StringList))
|
||||
{
|
||||
$this->stringlist_($parent_id, $semantics);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->scanner_->nextTokenIs(SieveToken::TestList))
|
||||
{
|
||||
$this->testlist_($parent_id, $semantics);
|
||||
}
|
||||
}
|
||||
|
||||
protected function stringlist_($parent_id, &$semantics)
|
||||
{
|
||||
if (!$this->scanner_->nextTokenIs(SieveToken::LeftBracket))
|
||||
{
|
||||
$this->string_($parent_id, $semantics);
|
||||
return;
|
||||
}
|
||||
|
||||
$token = $this->scanner_->nextToken();
|
||||
$semantics->startStringList($token);
|
||||
$this->tree_->addChildTo($parent_id, $token);
|
||||
|
||||
if($this->scanner_->nextTokenIs(SieveToken::RightBracket)) {
|
||||
//allow empty lists
|
||||
$token = $this->scanner_->nextToken();
|
||||
$this->tree_->addChildTo($parent_id, $token);
|
||||
$semantics->endStringList();
|
||||
return;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
$this->string_($parent_id, $semantics);
|
||||
$token = $this->scanner_->nextToken();
|
||||
|
||||
if (!$token->is(SieveToken::Comma|SieveToken::RightBracket))
|
||||
throw new SieveException($token, array(SieveToken::Comma, SieveToken::RightBracket));
|
||||
|
||||
if ($token->is(SieveToken::Comma))
|
||||
$semantics->continueStringList();
|
||||
|
||||
$this->tree_->addChildTo($parent_id, $token);
|
||||
}
|
||||
while (!$token->is(SieveToken::RightBracket));
|
||||
|
||||
$semantics->endStringList();
|
||||
}
|
||||
|
||||
protected function string_($parent_id, &$semantics)
|
||||
{
|
||||
$token = $this->scanner_->nextToken();
|
||||
$semantics->validateToken($token);
|
||||
$this->tree_->addChildTo($parent_id, $token);
|
||||
}
|
||||
|
||||
protected function testlist_($parent_id, &$semantics)
|
||||
{
|
||||
if (!$this->scanner_->nextTokenIs(SieveToken::LeftParenthesis))
|
||||
{
|
||||
$this->test_($parent_id, $semantics);
|
||||
return;
|
||||
}
|
||||
|
||||
$token = $this->scanner_->nextToken();
|
||||
$semantics->validateToken($token);
|
||||
$this->tree_->addChildTo($parent_id, $token);
|
||||
|
||||
do
|
||||
{
|
||||
$this->test_($parent_id, $semantics);
|
||||
|
||||
$token = $this->scanner_->nextToken();
|
||||
if (!$token->is(SieveToken::Comma|SieveToken::RightParenthesis))
|
||||
{
|
||||
throw new SieveException($token, array(SieveToken::Comma, SieveToken::RightParenthesis));
|
||||
}
|
||||
$this->tree_->addChildTo($parent_id, $token);
|
||||
}
|
||||
while (!$token->is(SieveToken::RightParenthesis));
|
||||
}
|
||||
|
||||
protected function test_($parent_id, &$semantics)
|
||||
{
|
||||
// Check if semantics allow an identifier
|
||||
$token = $this->scanner_->nextToken();
|
||||
$semantics->validateToken($token);
|
||||
|
||||
// Get semantics for this test command
|
||||
$this_semantics = new SieveSemantics($token, $this->getPrevToken_($parent_id));
|
||||
$this_node = $this->tree_->addChildTo($parent_id, $token);
|
||||
|
||||
// Consume eventual argument tokens
|
||||
$this->arguments_($this_node, $this_semantics);
|
||||
|
||||
// Check that all required arguments were there
|
||||
$token = $this->scanner_->peekNextToken();
|
||||
$this_semantics->done($token);
|
||||
}
|
||||
|
||||
protected function block_($parent_id, &$semantics)
|
||||
{
|
||||
$this->commands_($parent_id, $semantics);
|
||||
|
||||
$token = $this->scanner_->nextToken();
|
||||
if (!$token->is(SieveToken::BlockEnd))
|
||||
{
|
||||
throw new SieveException($token, SieveToken::BlockEnd);
|
||||
}
|
||||
$this->tree_->addChildTo($parent_id, $token);
|
||||
}
|
||||
}
|
145
data/web/inc/lib/sieve/SieveScanner.php
Normal file
145
data/web/inc/lib/sieve/SieveScanner.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php namespace Sieve;
|
||||
|
||||
include_once('SieveToken.php');
|
||||
|
||||
class SieveScanner
|
||||
{
|
||||
public function __construct(&$script)
|
||||
{
|
||||
if ($script === null)
|
||||
return;
|
||||
|
||||
$this->tokenize($script);
|
||||
}
|
||||
|
||||
public function setPassthroughFunc($callback)
|
||||
{
|
||||
if ($callback == null || is_callable($callback))
|
||||
$this->ptFn_ = $callback;
|
||||
}
|
||||
|
||||
public function tokenize(&$script)
|
||||
{
|
||||
$pos = 0;
|
||||
$line = 1;
|
||||
|
||||
$scriptLength = mb_strlen($script);
|
||||
|
||||
$unprocessedScript = $script;
|
||||
|
||||
|
||||
//create one regex to find the right match
|
||||
//avoids looping over all possible tokens: increases performance
|
||||
$nameToType = [];
|
||||
$regex = [];
|
||||
// chr(65) == 'A'
|
||||
$i = 65;
|
||||
|
||||
foreach ($this->tokenMatch_ as $type => $subregex) {
|
||||
$nameToType[chr($i)] = $type;
|
||||
$regex[] = "(?P<". chr($i) . ">^$subregex)";
|
||||
$i++;
|
||||
}
|
||||
|
||||
$regex = '/' . join('|', $regex) . '/';
|
||||
|
||||
while ($pos < $scriptLength)
|
||||
{
|
||||
if (preg_match($regex, $unprocessedScript, $match)) {
|
||||
|
||||
// only keep the group that match and we only want matches with group names
|
||||
// we can use the group name to find the token type using nameToType
|
||||
$filterMatch = array_filter(array_filter($match), 'is_string', ARRAY_FILTER_USE_KEY);
|
||||
|
||||
// the first element in filterMatch will contain the matched group and the key will be the name
|
||||
$type = $nameToType[key($filterMatch)];
|
||||
$currentMatch = current($filterMatch);
|
||||
|
||||
//create the token
|
||||
$token = new SieveToken($type, $currentMatch, $line);
|
||||
$this->tokens_[] = $token;
|
||||
|
||||
if ($type == SieveToken::Unknown)
|
||||
return;
|
||||
|
||||
// just remove the part that we parsed: don't extract the new substring using script length
|
||||
// as mb_strlen is \theta(pos) (it's linear in the position)
|
||||
$matchLength = mb_strlen($currentMatch);
|
||||
$unprocessedScript = mb_substr($unprocessedScript, $matchLength);
|
||||
|
||||
$pos += $matchLength;
|
||||
$line += mb_substr_count($currentMatch, "\n");
|
||||
} else {
|
||||
$this->tokens_[] = new SieveToken(SieveToken::Unknown, '', $line);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->tokens_[] = new SieveToken(SieveToken::ScriptEnd, '', $line);
|
||||
}
|
||||
|
||||
public function nextTokenIs($type)
|
||||
{
|
||||
return $this->peekNextToken()->is($type);
|
||||
}
|
||||
|
||||
public function peekNextToken()
|
||||
{
|
||||
$offset = 0;
|
||||
do {
|
||||
$next = $this->tokens_[$this->tokenPos_ + $offset++];
|
||||
} while ($next->is(SieveToken::Comment|SieveToken::Whitespace));
|
||||
|
||||
return $next;
|
||||
}
|
||||
|
||||
public function nextToken()
|
||||
{
|
||||
$token = $this->tokens_[$this->tokenPos_++];
|
||||
|
||||
while ($token->is(SieveToken::Comment|SieveToken::Whitespace))
|
||||
{
|
||||
if ($this->ptFn_ != null)
|
||||
call_user_func($this->ptFn_, $token);
|
||||
|
||||
$token = $this->tokens_[$this->tokenPos_++];
|
||||
}
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
protected $ptFn_ = null;
|
||||
protected $tokenPos_ = 0;
|
||||
protected $tokens_ = array();
|
||||
protected $tokenMatch_ = array (
|
||||
SieveToken::LeftBracket => '\[',
|
||||
SieveToken::RightBracket => '\]',
|
||||
SieveToken::BlockStart => '\{',
|
||||
SieveToken::BlockEnd => '\}',
|
||||
SieveToken::LeftParenthesis => '\(',
|
||||
SieveToken::RightParenthesis => '\)',
|
||||
SieveToken::Comma => ',',
|
||||
SieveToken::Semicolon => ';',
|
||||
SieveToken::Whitespace => '[ \r\n\t]+',
|
||||
SieveToken::Tag => ':[[:alpha:]_][[:alnum:]_]*(?=\b)',
|
||||
/*
|
||||
" # match a quotation mark
|
||||
( # start matching parts that include an escaped quotation mark
|
||||
([^"]*[^"\\\\]) # match a string without quotation marks and not ending with a backlash
|
||||
? # this also includes the empty string
|
||||
(\\\\\\\\)* # match any groups of even number of backslashes
|
||||
# (thus the character after these groups are not escaped)
|
||||
\\\\" # match an escaped quotation mark
|
||||
)* # accept any number of strings that end with an escaped quotation mark
|
||||
[^"]* # accept any trailing part that does not contain any quotation marks
|
||||
" # end of the quoted string
|
||||
*/
|
||||
SieveToken::QuotedString => '"(([^"]*[^"\\\\])?(\\\\\\\\)*\\\\")*[^"]*"',
|
||||
SieveToken::Number => '[[:digit:]]+(?:[KMG])?(?=\b)',
|
||||
SieveToken::Comment => '(?:\/\*(?:[^\*]|\*(?=[^\/]))*\*\/|#[^\r\n]*\r?(\n|$))',
|
||||
SieveToken::MultilineString => 'text:[ \t]*(?:#[^\r\n]*)?\r?\n(\.[^\r\n]+\r?\n|[^\.][^\r\n]*\r?\n)*\.\r?(\n|$)',
|
||||
SieveToken::Identifier => '[[:alpha:]_][[:alnum:]_]*(?=\b)',
|
||||
SieveToken::Unknown => '[^ \r\n\t]+'
|
||||
);
|
||||
}
|
6
data/web/inc/lib/sieve/SieveScript.php
Normal file
6
data/web/inc/lib/sieve/SieveScript.php
Normal file
@@ -0,0 +1,6 @@
|
||||
<?php namespace Sieve;
|
||||
|
||||
class SieveScript
|
||||
{
|
||||
// TODO: implement
|
||||
}
|
611
data/web/inc/lib/sieve/SieveSemantics.php
Normal file
611
data/web/inc/lib/sieve/SieveSemantics.php
Normal file
@@ -0,0 +1,611 @@
|
||||
<?php namespace Sieve;
|
||||
|
||||
require_once('SieveKeywordRegistry.php');
|
||||
require_once('SieveToken.php');
|
||||
require_once('SieveException.php');
|
||||
|
||||
class SieveSemantics
|
||||
{
|
||||
protected static $requiredExtensions_ = array();
|
||||
|
||||
protected $comparator_;
|
||||
protected $matchType_;
|
||||
protected $addressPart_;
|
||||
protected $tags_ = array();
|
||||
protected $arguments_;
|
||||
protected $deps_ = array();
|
||||
protected $followupToken_;
|
||||
|
||||
public function __construct($token, $prevToken)
|
||||
{
|
||||
$this->registry_ = SieveKeywordRegistry::get();
|
||||
$command = strtolower($token->text);
|
||||
|
||||
// Check the registry for $command
|
||||
if ($this->registry_->isCommand($command))
|
||||
{
|
||||
$xml = $this->registry_->command($command);
|
||||
$this->arguments_ = $this->makeArguments_($xml);
|
||||
$this->followupToken_ = SieveToken::Semicolon;
|
||||
}
|
||||
else if ($this->registry_->isTest($command))
|
||||
{
|
||||
$xml = $this->registry_->test($command);
|
||||
$this->arguments_ = $this->makeArguments_($xml);
|
||||
$this->followupToken_ = SieveToken::BlockStart;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new SieveException($token, 'unknown command '. $command);
|
||||
}
|
||||
|
||||
// Check if command may appear at this position within the script
|
||||
if ($this->registry_->isTest($command))
|
||||
{
|
||||
if (is_null($prevToken))
|
||||
throw new SieveException($token, $command .' may not appear as first command');
|
||||
|
||||
if (!preg_match('/^(if|elsif|anyof|allof|not)$/i', $prevToken->text))
|
||||
throw new SieveException($token, $command .' may not appear after '. $prevToken->text);
|
||||
}
|
||||
else if (isset($prevToken))
|
||||
{
|
||||
switch ($command)
|
||||
{
|
||||
case 'require':
|
||||
$valid_after = 'require';
|
||||
break;
|
||||
case 'elsif':
|
||||
case 'else':
|
||||
$valid_after = '(if|elsif)';
|
||||
break;
|
||||
default:
|
||||
$valid_after = $this->commandsRegex_();
|
||||
}
|
||||
|
||||
if (!preg_match('/^'. $valid_after .'$/i', $prevToken->text))
|
||||
throw new SieveException($token, $command .' may not appear after '. $prevToken->text);
|
||||
}
|
||||
|
||||
// Check for extension arguments to add to the command
|
||||
foreach ($this->registry_->arguments($command) as $arg)
|
||||
{
|
||||
switch ((string) $arg['type'])
|
||||
{
|
||||
case 'tag':
|
||||
array_unshift($this->arguments_, array(
|
||||
'type' => SieveToken::Tag,
|
||||
'occurrence' => $this->occurrence_($arg),
|
||||
'regex' => $this->regex_($arg),
|
||||
'call' => 'tagHook_',
|
||||
'name' => $this->name_($arg),
|
||||
'subArgs' => $this->makeArguments_($arg->children())
|
||||
));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->registry_->put();
|
||||
}
|
||||
|
||||
// TODO: the *Regex functions could possibly also be static properties
|
||||
protected function requireStringsRegex_()
|
||||
{
|
||||
return '('. implode('|', $this->registry_->requireStrings()) .')';
|
||||
}
|
||||
|
||||
protected function matchTypeRegex_()
|
||||
{
|
||||
return '('. implode('|', $this->registry_->matchTypes()) .')';
|
||||
}
|
||||
|
||||
protected function addressPartRegex_()
|
||||
{
|
||||
return '('. implode('|', $this->registry_->addressParts()) .')';
|
||||
}
|
||||
|
||||
protected function commandsRegex_()
|
||||
{
|
||||
return '('. implode('|', $this->registry_->commands()) .')';
|
||||
}
|
||||
|
||||
protected function testsRegex_()
|
||||
{
|
||||
return '('. implode('|', $this->registry_->tests()) .')';
|
||||
}
|
||||
|
||||
protected function comparatorRegex_()
|
||||
{
|
||||
return '('. implode('|', $this->registry_->comparators()) .')';
|
||||
}
|
||||
|
||||
protected function occurrence_($arg)
|
||||
{
|
||||
if (isset($arg['occurrence']))
|
||||
{
|
||||
switch ((string) $arg['occurrence'])
|
||||
{
|
||||
case 'optional':
|
||||
return '?';
|
||||
case 'any':
|
||||
return '*';
|
||||
case 'some':
|
||||
return '+';
|
||||
}
|
||||
}
|
||||
return '1';
|
||||
}
|
||||
|
||||
protected function name_($arg)
|
||||
{
|
||||
if (isset($arg['name']))
|
||||
{
|
||||
return (string) $arg['name'];
|
||||
}
|
||||
return (string) $arg['type'];
|
||||
}
|
||||
|
||||
protected function regex_($arg)
|
||||
{
|
||||
if (isset($arg['regex']))
|
||||
{
|
||||
return (string) $arg['regex'];
|
||||
}
|
||||
return '.*';
|
||||
}
|
||||
|
||||
protected function case_($arg)
|
||||
{
|
||||
if (isset($arg['case']))
|
||||
{
|
||||
return (string) $arg['case'];
|
||||
}
|
||||
return 'adhere';
|
||||
}
|
||||
|
||||
protected function follows_($arg)
|
||||
{
|
||||
if (isset($arg['follows']))
|
||||
{
|
||||
return (string) $arg['follows'];
|
||||
}
|
||||
return '.*';
|
||||
}
|
||||
|
||||
protected function makeValue_($arg)
|
||||
{
|
||||
if (isset($arg->value))
|
||||
{
|
||||
$res = $this->makeArguments_($arg->value);
|
||||
return array_shift($res);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an extension (test) commands parameters from XML to
|
||||
* a PHP array the {@see Semantics} class understands.
|
||||
* @param array(SimpleXMLElement) $parameters
|
||||
* @return array
|
||||
*/
|
||||
protected function makeArguments_($parameters)
|
||||
{
|
||||
$arguments = array();
|
||||
|
||||
foreach ($parameters as $arg)
|
||||
{
|
||||
// Ignore anything not a <parameter>
|
||||
if ($arg->getName() != 'parameter')
|
||||
continue;
|
||||
|
||||
switch ((string) $arg['type'])
|
||||
{
|
||||
case 'addresspart':
|
||||
array_push($arguments, array(
|
||||
'type' => SieveToken::Tag,
|
||||
'occurrence' => $this->occurrence_($arg),
|
||||
'regex' => $this->addressPartRegex_(),
|
||||
'call' => 'addressPartHook_',
|
||||
'name' => 'address part',
|
||||
'subArgs' => $this->makeArguments_($arg)
|
||||
));
|
||||
break;
|
||||
|
||||
case 'block':
|
||||
array_push($arguments, array(
|
||||
'type' => SieveToken::BlockStart,
|
||||
'occurrence' => '1',
|
||||
'regex' => '{',
|
||||
'name' => 'block',
|
||||
'subArgs' => $this->makeArguments_($arg)
|
||||
));
|
||||
break;
|
||||
|
||||
case 'comparator':
|
||||
array_push($arguments, array(
|
||||
'type' => SieveToken::Tag,
|
||||
'occurrence' => $this->occurrence_($arg),
|
||||
'regex' => 'comparator',
|
||||
'name' => 'comparator',
|
||||
'subArgs' => array( array(
|
||||
'type' => SieveToken::String,
|
||||
'occurrence' => '1',
|
||||
'call' => 'comparatorHook_',
|
||||
'case' => 'adhere',
|
||||
'regex' => $this->comparatorRegex_(),
|
||||
'name' => 'comparator string',
|
||||
'follows' => 'comparator'
|
||||
))
|
||||
));
|
||||
break;
|
||||
|
||||
case 'matchtype':
|
||||
array_push($arguments, array(
|
||||
'type' => SieveToken::Tag,
|
||||
'occurrence' => $this->occurrence_($arg),
|
||||
'regex' => $this->matchTypeRegex_(),
|
||||
'call' => 'matchTypeHook_',
|
||||
'name' => 'match type',
|
||||
'subArgs' => $this->makeArguments_($arg)
|
||||
));
|
||||
break;
|
||||
|
||||
case 'number':
|
||||
array_push($arguments, array(
|
||||
'type' => SieveToken::Number,
|
||||
'occurrence' => $this->occurrence_($arg),
|
||||
'regex' => $this->regex_($arg),
|
||||
'name' => $this->name_($arg),
|
||||
'follows' => $this->follows_($arg)
|
||||
));
|
||||
break;
|
||||
|
||||
case 'requirestrings':
|
||||
array_push($arguments, array(
|
||||
'type' => SieveToken::StringList,
|
||||
'occurrence' => $this->occurrence_($arg),
|
||||
'call' => 'setRequire_',
|
||||
'case' => 'adhere',
|
||||
'regex' => $this->requireStringsRegex_(),
|
||||
'name' => $this->name_($arg)
|
||||
));
|
||||
break;
|
||||
|
||||
case 'string':
|
||||
array_push($arguments, array(
|
||||
'type' => SieveToken::String,
|
||||
'occurrence' => $this->occurrence_($arg),
|
||||
'regex' => $this->regex_($arg),
|
||||
'case' => $this->case_($arg),
|
||||
'name' => $this->name_($arg),
|
||||
'follows' => $this->follows_($arg)
|
||||
));
|
||||
break;
|
||||
|
||||
case 'stringlist':
|
||||
array_push($arguments, array(
|
||||
'type' => SieveToken::StringList,
|
||||
'occurrence' => $this->occurrence_($arg),
|
||||
'regex' => $this->regex_($arg),
|
||||
'case' => $this->case_($arg),
|
||||
'name' => $this->name_($arg),
|
||||
'follows' => $this->follows_($arg)
|
||||
));
|
||||
break;
|
||||
|
||||
case 'tag':
|
||||
array_push($arguments, array(
|
||||
'type' => SieveToken::Tag,
|
||||
'occurrence' => $this->occurrence_($arg),
|
||||
'regex' => $this->regex_($arg),
|
||||
'call' => 'tagHook_',
|
||||
'name' => $this->name_($arg),
|
||||
'subArgs' => $this->makeArguments_($arg->children()),
|
||||
'follows' => $this->follows_($arg)
|
||||
));
|
||||
break;
|
||||
|
||||
case 'test':
|
||||
array_push($arguments, array(
|
||||
'type' => SieveToken::Identifier,
|
||||
'occurrence' => $this->occurrence_($arg),
|
||||
'regex' => $this->testsRegex_(),
|
||||
'name' => $this->name_($arg),
|
||||
'subArgs' => $this->makeArguments_($arg->children())
|
||||
));
|
||||
break;
|
||||
|
||||
case 'testlist':
|
||||
array_push($arguments, array(
|
||||
'type' => SieveToken::LeftParenthesis,
|
||||
'occurrence' => '1',
|
||||
'regex' => '\(',
|
||||
'name' => $this->name_($arg),
|
||||
'subArgs' => null
|
||||
));
|
||||
array_push($arguments, array(
|
||||
'type' => SieveToken::Identifier,
|
||||
'occurrence' => '+',
|
||||
'regex' => $this->testsRegex_(),
|
||||
'name' => $this->name_($arg),
|
||||
'subArgs' => $this->makeArguments_($arg->children())
|
||||
));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add argument(s) expected / allowed to appear next.
|
||||
* @param array $value
|
||||
*/
|
||||
protected function addArguments_($identifier, $subArgs)
|
||||
{
|
||||
for ($i = count($subArgs); $i > 0; $i--)
|
||||
{
|
||||
$arg = $subArgs[$i-1];
|
||||
if (preg_match('/^'. $arg['follows'] .'$/si', $identifier))
|
||||
array_unshift($this->arguments_, $arg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add dependency that is expected to be fullfilled when parsing
|
||||
* of the current command is {@see done}.
|
||||
* @param array $dependency
|
||||
*/
|
||||
protected function addDependency_($type, $name, $dependencies)
|
||||
{
|
||||
foreach ($dependencies as $d)
|
||||
{
|
||||
array_push($this->deps_, array(
|
||||
'o_type' => $type,
|
||||
'o_name' => $name,
|
||||
'type' => $d['type'],
|
||||
'name' => $d['name'],
|
||||
'regex' => $d['regex']
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
protected function invoke_($token, $func, $arg = array())
|
||||
{
|
||||
if (!is_array($arg))
|
||||
$arg = array($arg);
|
||||
|
||||
$err = call_user_func_array(array(&$this, $func), $arg);
|
||||
|
||||
if ($err)
|
||||
throw new SieveException($token, $err);
|
||||
}
|
||||
|
||||
protected function setRequire_($extension)
|
||||
{
|
||||
array_push(self::$requiredExtensions_, $extension);
|
||||
$this->registry_->activate($extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook function that is called after a address part match was found
|
||||
* in a command. The kind of address part is remembered in case it's
|
||||
* needed later {@see done}. For address parts from a extension
|
||||
* dependency information and valid values are looked up as well.
|
||||
* @param string $addresspart
|
||||
*/
|
||||
protected function addressPartHook_($addresspart)
|
||||
{
|
||||
$this->addressPart_ = $addresspart;
|
||||
$xml = $this->registry_->addresspart($this->addressPart_);
|
||||
|
||||
if (isset($xml))
|
||||
{
|
||||
// Add possible value and dependancy
|
||||
$this->addArguments_($this->addressPart_, $this->makeArguments_($xml));
|
||||
$this->addDependency_('address part', $this->addressPart_, $xml->requires);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook function that is called after a match type was found in a
|
||||
* command. The kind of match type is remembered in case it's
|
||||
* needed later {@see done}. For a match type from extensions
|
||||
* dependency information and valid values are looked up as well.
|
||||
* @param string $matchtype
|
||||
*/
|
||||
protected function matchTypeHook_($matchtype)
|
||||
{
|
||||
$this->matchType_ = $matchtype;
|
||||
$xml = $this->registry_->matchtype($this->matchType_);
|
||||
|
||||
if (isset($xml))
|
||||
{
|
||||
// Add possible value and dependancy
|
||||
$this->addArguments_($this->matchType_, $this->makeArguments_($xml));
|
||||
$this->addDependency_('match type', $this->matchType_, $xml->requires);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook function that is called after a comparator was found in
|
||||
* a command. The comparator is remembered in case it's needed for
|
||||
* comparsion later {@see done}. For a comparator from extensions
|
||||
* dependency information is looked up as well.
|
||||
* @param string $comparator
|
||||
*/
|
||||
protected function comparatorHook_($comparator)
|
||||
{
|
||||
$this->comparator_ = $comparator;
|
||||
$xml = $this->registry_->comparator($this->comparator_);
|
||||
|
||||
if (isset($xml))
|
||||
{
|
||||
// Add possible dependancy
|
||||
$this->addDependency_('comparator', $this->comparator_, $xml->requires);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook function that is called after a tag was found in
|
||||
* a command. The tag is remembered in case it's needed for
|
||||
* comparsion later {@see done}. For a tags from extensions
|
||||
* dependency information is looked up as well.
|
||||
* @param string $tag
|
||||
*/
|
||||
protected function tagHook_($tag)
|
||||
{
|
||||
array_push($this->tags_, $tag);
|
||||
$xml = $this->registry_->argument($tag);
|
||||
|
||||
// Add possible dependancies
|
||||
if (isset($xml))
|
||||
$this->addDependency_('tag', $tag, $xml->requires);
|
||||
}
|
||||
|
||||
protected function validType_($token)
|
||||
{
|
||||
foreach ($this->arguments_ as $arg)
|
||||
{
|
||||
if ($arg['occurrence'] == '0')
|
||||
{
|
||||
array_shift($this->arguments_);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->is($arg['type']))
|
||||
return;
|
||||
|
||||
// Is the argument required
|
||||
if ($arg['occurrence'] != '?' && $arg['occurrence'] != '*')
|
||||
throw new SieveException($token, $arg['type']);
|
||||
|
||||
array_shift($this->arguments_);
|
||||
}
|
||||
|
||||
// Check if command expects any (more) arguments
|
||||
if (empty($this->arguments_))
|
||||
throw new SieveException($token, $this->followupToken_);
|
||||
|
||||
throw new SieveException($token, 'unexpected '. SieveToken::typeString($token->type) .' '. $token->text);
|
||||
}
|
||||
|
||||
public function startStringList($token)
|
||||
{
|
||||
$this->validType_($token);
|
||||
$this->arguments_[0]['type'] = SieveToken::String;
|
||||
$this->arguments_[0]['occurrence'] = '+';
|
||||
}
|
||||
|
||||
public function continueStringList()
|
||||
{
|
||||
$this->arguments_[0]['occurrence'] = '+';
|
||||
}
|
||||
|
||||
public function endStringList()
|
||||
{
|
||||
array_shift($this->arguments_);
|
||||
}
|
||||
|
||||
public function validateToken($token)
|
||||
{
|
||||
// Make sure the argument has a valid type
|
||||
$this->validType_($token);
|
||||
|
||||
foreach ($this->arguments_ as &$arg)
|
||||
{
|
||||
// Build regular expression according to argument type
|
||||
switch ($arg['type'])
|
||||
{
|
||||
case SieveToken::String:
|
||||
case SieveToken::StringList:
|
||||
$regex = '/^(?:text:[^\n]*\n(?P<one>'. $arg['regex'] .')\.\r?\n?|"(?P<two>'. $arg['regex'] .')")$/'
|
||||
. ($arg['case'] == 'ignore' ? 'si' : 's');
|
||||
break;
|
||||
case SieveToken::Tag:
|
||||
$regex = '/^:(?P<one>'. $arg['regex'] .')$/si';
|
||||
break;
|
||||
default:
|
||||
$regex = '/^(?P<one>'. $arg['regex'] .')$/si';
|
||||
}
|
||||
|
||||
if (preg_match($regex, $token->text, $match))
|
||||
{
|
||||
$text = ($match['one'] ? $match['one'] : $match['two']);
|
||||
|
||||
// Add argument(s) that may now appear after this one
|
||||
if (isset($arg['subArgs']))
|
||||
$this->addArguments_($text, $arg['subArgs']);
|
||||
|
||||
// Call extra processing function if defined
|
||||
if (isset($arg['call']))
|
||||
$this->invoke_($token, $arg['call'], $text);
|
||||
|
||||
// Check if a possible value of this argument may occur
|
||||
if ($arg['occurrence'] == '?' || $arg['occurrence'] == '1')
|
||||
{
|
||||
$arg['occurrence'] = '0';
|
||||
}
|
||||
else if ($arg['occurrence'] == '+')
|
||||
{
|
||||
$arg['occurrence'] = '*';
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($token->is($arg['type']) && $arg['occurrence'] == 1)
|
||||
{
|
||||
throw new SieveException($token,
|
||||
SieveToken::typeString($token->type) ." $token->text where ". $arg['name'] .' expected');
|
||||
}
|
||||
}
|
||||
|
||||
throw new SieveException($token, 'unexpected '. SieveToken::typeString($token->type) .' '. $token->text);
|
||||
}
|
||||
|
||||
public function done($token)
|
||||
{
|
||||
// Check if there are required arguments left
|
||||
foreach ($this->arguments_ as $arg)
|
||||
{
|
||||
if ($arg['occurrence'] == '+' || $arg['occurrence'] == '1')
|
||||
throw new SieveException($token, $arg['type']);
|
||||
}
|
||||
|
||||
// Check if the command depends on use of a certain tag
|
||||
foreach ($this->deps_ as $d)
|
||||
{
|
||||
switch ($d['type'])
|
||||
{
|
||||
case 'addresspart':
|
||||
$values = array($this->addressPart_);
|
||||
break;
|
||||
|
||||
case 'matchtype':
|
||||
$values = array($this->matchType_);
|
||||
break;
|
||||
|
||||
case 'comparator':
|
||||
$values = array($this->comparator_);
|
||||
break;
|
||||
|
||||
case 'tag':
|
||||
$values = $this->tags_;
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($values as $value)
|
||||
{
|
||||
if (preg_match('/^'. $d['regex'] .'$/mi', $value))
|
||||
break 2;
|
||||
}
|
||||
|
||||
throw new SieveException($token,
|
||||
$d['o_type'] .' '. $d['o_name'] .' requires use of '. $d['type'] .' '. $d['name']);
|
||||
}
|
||||
}
|
||||
}
|
88
data/web/inc/lib/sieve/SieveToken.php
Normal file
88
data/web/inc/lib/sieve/SieveToken.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php namespace Sieve;
|
||||
|
||||
include_once('SieveDumpable.php');
|
||||
|
||||
class SieveToken implements SieveDumpable
|
||||
{
|
||||
const Unknown = 0x0000;
|
||||
const ScriptEnd = 0x0001;
|
||||
const LeftBracket = 0x0002;
|
||||
const RightBracket = 0x0004;
|
||||
const BlockStart = 0x0008;
|
||||
const BlockEnd = 0x0010;
|
||||
const LeftParenthesis = 0x0020;
|
||||
const RightParenthesis = 0x0040;
|
||||
const Comma = 0x0080;
|
||||
const Semicolon = 0x0100;
|
||||
const Whitespace = 0x0200;
|
||||
const Tag = 0x0400;
|
||||
const QuotedString = 0x0800;
|
||||
const Number = 0x1000;
|
||||
const Comment = 0x2000;
|
||||
const MultilineString = 0x4000;
|
||||
const Identifier = 0x8000;
|
||||
|
||||
const String = 0x4800; // Quoted | Multiline
|
||||
const StringList = 0x4802; // Quoted | Multiline | LeftBracket
|
||||
const StringListSep = 0x0084; // Comma | RightBracket
|
||||
const Unparsed = 0x2200; // Comment | Whitespace
|
||||
const TestList = 0x8020; // Identifier | LeftParenthesis
|
||||
|
||||
public $type;
|
||||
public $text;
|
||||
public $line;
|
||||
|
||||
public function __construct($type, $text, $line)
|
||||
{
|
||||
$this->text = $text;
|
||||
$this->type = $type;
|
||||
$this->line = intval($line);
|
||||
}
|
||||
|
||||
public function dump()
|
||||
{
|
||||
return '<'. SieveToken::escape($this->text) .'> type:'. SieveToken::typeString($this->type) .' line:'. $this->line;
|
||||
}
|
||||
|
||||
public function text()
|
||||
{
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
public function is($type)
|
||||
{
|
||||
return (bool)($this->type & $type);
|
||||
}
|
||||
|
||||
public static function typeString($type)
|
||||
{
|
||||
switch ($type)
|
||||
{
|
||||
case SieveToken::Identifier: return 'identifier';
|
||||
case SieveToken::Whitespace: return 'whitespace';
|
||||
case SieveToken::QuotedString: return 'quoted string';
|
||||
case SieveToken::Tag: return 'tag';
|
||||
case SieveToken::Semicolon: return 'semicolon';
|
||||
case SieveToken::LeftBracket: return 'left bracket';
|
||||
case SieveToken::RightBracket: return 'right bracket';
|
||||
case SieveToken::BlockStart: return 'block start';
|
||||
case SieveToken::BlockEnd: return 'block end';
|
||||
case SieveToken::LeftParenthesis: return 'left parenthesis';
|
||||
case SieveToken::RightParenthesis: return 'right parenthesis';
|
||||
case SieveToken::Comma: return 'comma';
|
||||
case SieveToken::Number: return 'number';
|
||||
case SieveToken::Comment: return 'comment';
|
||||
case SieveToken::MultilineString: return 'multiline string';
|
||||
case SieveToken::ScriptEnd: return 'script end';
|
||||
case SieveToken::String: return 'string';
|
||||
case SieveToken::StringList: return 'string list';
|
||||
default: return 'unknown token';
|
||||
}
|
||||
}
|
||||
|
||||
protected static $tr_ = array("\r" => '\r', "\n" => '\n', "\t" => '\t');
|
||||
public static function escape($val)
|
||||
{
|
||||
return strtr($val, self::$tr_);
|
||||
}
|
||||
}
|
117
data/web/inc/lib/sieve/SieveTree.php
Normal file
117
data/web/inc/lib/sieve/SieveTree.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php namespace Sieve;
|
||||
|
||||
class SieveTree
|
||||
{
|
||||
protected $childs_;
|
||||
protected $parents_;
|
||||
protected $nodes_;
|
||||
protected $max_id_;
|
||||
protected $dump_;
|
||||
|
||||
public function __construct($name = 'tree')
|
||||
{
|
||||
$this->childs_ = array();
|
||||
$this->parents_ = array();
|
||||
$this->nodes_ = array();
|
||||
$this->max_id_ = 0;
|
||||
|
||||
$this->parents_[0] = null;
|
||||
$this->nodes_[0] = $name;
|
||||
}
|
||||
|
||||
public function addChild(SieveDumpable $child)
|
||||
{
|
||||
return $this->addChildTo($this->max_id_, $child);
|
||||
}
|
||||
|
||||
public function addChildTo($parent_id, SieveDumpable $child)
|
||||
{
|
||||
if (!is_int($parent_id)
|
||||
|| !isset($this->nodes_[$parent_id]))
|
||||
return null;
|
||||
|
||||
if (!isset($this->childs_[$parent_id]))
|
||||
$this->childs_[$parent_id] = array();
|
||||
|
||||
$child_id = ++$this->max_id_;
|
||||
$this->nodes_[$child_id] = $child;
|
||||
$this->parents_[$child_id] = $parent_id;
|
||||
array_push($this->childs_[$parent_id], $child_id);
|
||||
|
||||
return $child_id;
|
||||
}
|
||||
|
||||
public function getRoot()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function getChilds($node_id)
|
||||
{
|
||||
if (!is_int($node_id)
|
||||
|| !isset($this->nodes_[$node_id]))
|
||||
return null;
|
||||
|
||||
if (!isset($this->childs_[$node_id]))
|
||||
return array();
|
||||
|
||||
return $this->childs_[$node_id];
|
||||
}
|
||||
|
||||
public function getNode($node_id)
|
||||
{
|
||||
if ($node_id == 0 || !is_int($node_id)
|
||||
|| !isset($this->nodes_[$node_id]))
|
||||
return null;
|
||||
|
||||
return $this->nodes_[$node_id];
|
||||
}
|
||||
|
||||
public function dump()
|
||||
{
|
||||
$this->dump_ = $this->nodes_[$this->getRoot()] ."\n";
|
||||
$this->dumpChilds_($this->getRoot(), ' ');
|
||||
return $this->dump_;
|
||||
}
|
||||
|
||||
protected function dumpChilds_($parent_id, $prefix)
|
||||
{
|
||||
if (!isset($this->childs_[$parent_id]))
|
||||
return;
|
||||
|
||||
$childs = $this->childs_[$parent_id];
|
||||
$last_child = count($childs);
|
||||
|
||||
for ($i=1; $i <= $last_child; ++$i)
|
||||
{
|
||||
$child_node = $this->nodes_[$childs[$i-1]];
|
||||
$infix = ($i == $last_child ? '`--- ' : '|--- ');
|
||||
$this->dump_ .= $prefix . $infix . $child_node->dump() . " (id:" . $childs[$i-1] . ")\n";
|
||||
|
||||
$next_prefix = $prefix . ($i == $last_child ? ' ' : '| ');
|
||||
$this->dumpChilds_($childs[$i-1], $next_prefix);
|
||||
}
|
||||
}
|
||||
|
||||
public function getText()
|
||||
{
|
||||
$this->dump_ = '';
|
||||
$this->childText_($this->getRoot());
|
||||
return $this->dump_;
|
||||
}
|
||||
|
||||
protected function childText_($parent_id)
|
||||
{
|
||||
if (!isset($this->childs_[$parent_id]))
|
||||
return;
|
||||
|
||||
$childs = $this->childs_[$parent_id];
|
||||
|
||||
for ($i = 0; $i < count($childs); ++$i)
|
||||
{
|
||||
$child_node = $this->nodes_[$childs[$i]];
|
||||
$this->dump_ .= $child_node->text();
|
||||
$this->childText_($childs[$i]);
|
||||
}
|
||||
}
|
||||
}
|
14
data/web/inc/lib/sieve/extensions/body.xml
Normal file
14
data/web/inc/lib/sieve/extensions/body.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="body">
|
||||
|
||||
<test name="body">
|
||||
<parameter type="matchtype" occurrence="optional" />
|
||||
<parameter type="comparator" occurrence="optional" />
|
||||
<parameter type="tag" name="body transform" regex="(raw|content|text)" occurrence="optional">
|
||||
<parameter type="stringlist" name="content types" follows="content" />
|
||||
</parameter>
|
||||
<parameter type="stringlist" name="key list" />
|
||||
</test>
|
||||
|
||||
</extension>
|
@@ -0,0 +1,7 @@
|
||||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="comparator-i;ascii-numeric">
|
||||
|
||||
<comparator name="i;ascii-numeric" />
|
||||
|
||||
</extension>
|
9
data/web/inc/lib/sieve/extensions/copy.xml
Normal file
9
data/web/inc/lib/sieve/extensions/copy.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="copy">
|
||||
|
||||
<tagged-argument extends="(fileinto|redirect)">
|
||||
<parameter type="tag" name="copy" regex="copy" occurrence="optional" />
|
||||
</tagged-argument>
|
||||
|
||||
</extension>
|
28
data/web/inc/lib/sieve/extensions/date.xml
Normal file
28
data/web/inc/lib/sieve/extensions/date.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="date">
|
||||
|
||||
<test name="date">
|
||||
<parameter type="matchtype" occurrence="optional" />
|
||||
<parameter type="comparator" occurrence="optional" />
|
||||
<parameter type="tag" name="zone" regex="(zone|originalzone)" occurrence="optional">
|
||||
<parameter type="string" name="time-zone" follows="zone" />
|
||||
</parameter>
|
||||
<parameter type="string" name="header-name" />
|
||||
<parameter type="string" case="ignore" name="date-part"
|
||||
regex="(year|month|day|date|julian|hour|minute|second|time|iso8601|std11|zone|weekday)" />
|
||||
<parameter type="stringlist" name="key-list" />
|
||||
</test>
|
||||
|
||||
<test name="currentdate">
|
||||
<parameter type="matchtype" occurrence="optional" />
|
||||
<parameter type="comparator" occurrence="optional" />
|
||||
<parameter type="tag" name="zone" regex="zone" occurrence="optional">
|
||||
<parameter type="string" name="time-zone" />
|
||||
</parameter>
|
||||
<parameter type="string" case="ignore" name="date-part"
|
||||
regex="(year|month|day|date|julian|hour|minute|second|time|iso8601|std11|zone|weekday)" />
|
||||
<parameter type="stringlist" name="key-list" />
|
||||
</test>
|
||||
|
||||
</extension>
|
22
data/web/inc/lib/sieve/extensions/editheader.xml
Normal file
22
data/web/inc/lib/sieve/extensions/editheader.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="editheader">
|
||||
|
||||
<command name="addheader">
|
||||
<parameter type="tag" name="last" regex="last" occurrence="optional" />
|
||||
<parameter type="string" name="field name" />
|
||||
<parameter type="string" name="value" />
|
||||
</command>
|
||||
|
||||
<command name="deleteheader">
|
||||
<parameter type="tag" name="index" regex="index" occurrence="optional">
|
||||
<parameter type="number" name="field number" />
|
||||
<parameter type="tag" name="last" regex="last" occurrence="optional" />
|
||||
</parameter>
|
||||
<parameter type="matchtype" occurrence="optional" />
|
||||
<parameter type="comparator" occurrence="optional" />
|
||||
<parameter type="string" name="field name" />
|
||||
<parameter type="stringlist" name="value patterns" occurrence="optional" />
|
||||
</command>
|
||||
|
||||
</extension>
|
13
data/web/inc/lib/sieve/extensions/envelope.xml
Normal file
13
data/web/inc/lib/sieve/extensions/envelope.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="envelope">
|
||||
|
||||
<test name="envelope">
|
||||
<parameter type="matchtype" occurrence="optional" />
|
||||
<parameter type="comparator" occurrence="optional" />
|
||||
<parameter type="addresspart" occurrence="optional" />
|
||||
<parameter type="stringlist" name="envelope-part" />
|
||||
<parameter type="stringlist" name="key" />
|
||||
</test>
|
||||
|
||||
</extension>
|
13
data/web/inc/lib/sieve/extensions/environment.xml
Normal file
13
data/web/inc/lib/sieve/extensions/environment.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="environment">
|
||||
|
||||
<test name="environment">
|
||||
<parameter type="matchtype" occurrence="optional" />
|
||||
<parameter type="comparator" occurrence="optional" />
|
||||
<parameter type="string" name="name"
|
||||
regex="(domain|host|location|name|phase|remote-host|remote-ip|version|vnd\..+)" />
|
||||
<parameter type="stringlist" name="key-list" />
|
||||
</test>
|
||||
|
||||
</extension>
|
11
data/web/inc/lib/sieve/extensions/ereject.xml
Normal file
11
data/web/inc/lib/sieve/extensions/ereject.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="ereject">
|
||||
|
||||
<command name="ereject">
|
||||
|
||||
<parameter type="string" name="reason" />
|
||||
|
||||
</command>
|
||||
|
||||
</extension>
|
9
data/web/inc/lib/sieve/extensions/fileinto.xml
Normal file
9
data/web/inc/lib/sieve/extensions/fileinto.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="fileinto">
|
||||
|
||||
<command name="fileinto">
|
||||
<parameter type="string" name="folder" />
|
||||
</command>
|
||||
|
||||
</extension>
|
29
data/web/inc/lib/sieve/extensions/imap4flags.xml
Normal file
29
data/web/inc/lib/sieve/extensions/imap4flags.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="imap4flags">
|
||||
|
||||
<command name="setflag">
|
||||
<parameter type="stringlist" name="flag list" />
|
||||
</command>
|
||||
|
||||
<command name="addflag">
|
||||
<parameter type="stringlist" name="flag list" />
|
||||
</command>
|
||||
|
||||
<command name="removeflag">
|
||||
<parameter type="stringlist" name="flag list" />
|
||||
</command>
|
||||
|
||||
<test name="hasflag">
|
||||
<parameter type="matchtype" occurrence="optional" />
|
||||
<parameter type="comparator" occurrence="optional" />
|
||||
<parameter type="stringlist" name="flag list" />
|
||||
</test>
|
||||
|
||||
<tagged-argument extends="(fileinto|keep)">
|
||||
<parameter type="tag" name="flags" regex="flags" occurrence="optional">
|
||||
<parameter type="stringlist" name="flag list" />
|
||||
</parameter>
|
||||
</tagged-argument>
|
||||
|
||||
</extension>
|
21
data/web/inc/lib/sieve/extensions/imapflags.xml
Normal file
21
data/web/inc/lib/sieve/extensions/imapflags.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="imapflags">
|
||||
|
||||
<command name="mark" />
|
||||
|
||||
<command name="unmark" />
|
||||
|
||||
<command name="setflag">
|
||||
<parameter type="stringlist" name="flag list" />
|
||||
</command>
|
||||
|
||||
<command name="addflag">
|
||||
<parameter type="stringlist" name="flag list" />
|
||||
</command>
|
||||
|
||||
<command name="removeflag">
|
||||
<parameter type="stringlist" name="flag list" />
|
||||
</command>
|
||||
|
||||
</extension>
|
17
data/web/inc/lib/sieve/extensions/index.xml
Normal file
17
data/web/inc/lib/sieve/extensions/index.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="index">
|
||||
|
||||
<tagged-argument extends="(header|address|date)">
|
||||
<parameter type="tag" name="index" regex="index" occurrence="optional">
|
||||
<parameter type="number" name="field number" />
|
||||
</parameter>
|
||||
</tagged-argument>
|
||||
|
||||
<tagged-argument extends="(header|address|date)">
|
||||
<parameter type="tag" name="last" regex="last" occurrence="optional">
|
||||
<requires type="tag" name="index" regex="index" />
|
||||
</parameter>
|
||||
</tagged-argument>
|
||||
|
||||
</extension>
|
29
data/web/inc/lib/sieve/extensions/notify.xml
Normal file
29
data/web/inc/lib/sieve/extensions/notify.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="notify">
|
||||
|
||||
<command name="notify">
|
||||
<parameter type="tag" name="method" regex="method" occurrence="optional">
|
||||
<parameter type="string" name="method-name" />
|
||||
</parameter>
|
||||
|
||||
<parameter type="tag" name="id" regex="id" occurrence="optional">
|
||||
<parameter type="string" name="message-id" />
|
||||
</parameter>
|
||||
|
||||
<parameter type="tag" name="priority" regex="(low|normal|high)" occurrence="optional" />
|
||||
|
||||
<parameter type="tag" name="message" regex="message" occurrence="optional">
|
||||
<parameter type="string" name="message-text" />
|
||||
</parameter>
|
||||
</command>
|
||||
|
||||
<command name="denotify">
|
||||
<parameter type="matchtype" occurrence="optional">
|
||||
<parameter type="string" name="message-id" />
|
||||
</parameter>
|
||||
|
||||
<parameter type="tag" name="priority" regex="(low|normal|high)" occurrence="optional" />
|
||||
</command>
|
||||
|
||||
</extension>
|
11
data/web/inc/lib/sieve/extensions/regex.xml
Normal file
11
data/web/inc/lib/sieve/extensions/regex.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="regex">
|
||||
|
||||
<matchtype name="regex" />
|
||||
|
||||
<tagged-argument extends="set">
|
||||
<parameter type="tag" name="modifier" regex="quoteregex" occurrence="optional" />
|
||||
</tagged-argument>
|
||||
|
||||
</extension>
|
11
data/web/inc/lib/sieve/extensions/reject.xml
Normal file
11
data/web/inc/lib/sieve/extensions/reject.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="reject">
|
||||
|
||||
<command name="reject">
|
||||
|
||||
<parameter type="string" name="reason" />
|
||||
|
||||
</command>
|
||||
|
||||
</extension>
|
14
data/web/inc/lib/sieve/extensions/relational.xml
Normal file
14
data/web/inc/lib/sieve/extensions/relational.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="relational">
|
||||
|
||||
<matchtype name="count">
|
||||
<requires type="comparator" name="i;ascii-numeric" regex="i;ascii-numeric" />
|
||||
<parameter type="string" name="relation string" regex="(lt|le|eq|ge|gt|ne)" />
|
||||
</matchtype>
|
||||
|
||||
<matchtype name="value">
|
||||
<parameter type="string" name="relation string" regex="(lt|le|eq|ge|gt|ne)" />
|
||||
</matchtype>
|
||||
|
||||
</extension>
|
11
data/web/inc/lib/sieve/extensions/spamtest.xml
Normal file
11
data/web/inc/lib/sieve/extensions/spamtest.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="spamtest">
|
||||
|
||||
<test name="spamtest">
|
||||
<parameter type="comparator" occurrence="optional" />
|
||||
<parameter type="matchtype" occurrence="optional" />
|
||||
<parameter type="string" name="value" />
|
||||
</test>
|
||||
|
||||
</extension>
|
12
data/web/inc/lib/sieve/extensions/spamtestplus.xml
Normal file
12
data/web/inc/lib/sieve/extensions/spamtestplus.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="spamtestplus">
|
||||
|
||||
<test name="spamtest" overrides="true">
|
||||
<parameter type="comparator" occurrence="optional" />
|
||||
<parameter type="matchtype" occurrence="optional" />
|
||||
<parameter type="tag" name="percent" regex="percent" occurrence="optional" />
|
||||
<parameter type="string" name="value" />
|
||||
</test>
|
||||
|
||||
</extension>
|
8
data/web/inc/lib/sieve/extensions/subaddress.xml
Normal file
8
data/web/inc/lib/sieve/extensions/subaddress.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="subaddress">
|
||||
|
||||
<addresspart name="user" />
|
||||
<addresspart name="detail" />
|
||||
|
||||
</extension>
|
31
data/web/inc/lib/sieve/extensions/vacation.xml
Normal file
31
data/web/inc/lib/sieve/extensions/vacation.xml
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="vacation">
|
||||
|
||||
<command name="vacation">
|
||||
<parameter type="tag" name="days" occurrence="optional" regex="days">
|
||||
<parameter type="number" name="period" />
|
||||
</parameter>
|
||||
|
||||
<parameter type="tag" name="addresses" occurrence="optional" regex="addresses">
|
||||
<parameter type="stringlist" name="address strings" />
|
||||
</parameter>
|
||||
|
||||
<parameter type="tag" name="subject" occurrence="optional" regex="subject">
|
||||
<parameter type="string" name="subject string" />
|
||||
</parameter>
|
||||
|
||||
<parameter type="tag" name="from" occurrence="optional" regex="from">
|
||||
<parameter type="string" name="from string" />
|
||||
</parameter>
|
||||
|
||||
<parameter type="tag" name="handle" occurrence="optional" regex="handle">
|
||||
<parameter type="string" name="handle string" />
|
||||
</parameter>
|
||||
|
||||
<parameter type="tag" name="mime" occurrence="optional" regex="mime" />
|
||||
|
||||
<parameter type="string" name="reason" />
|
||||
</command>
|
||||
|
||||
</extension>
|
21
data/web/inc/lib/sieve/extensions/variables.xml
Normal file
21
data/web/inc/lib/sieve/extensions/variables.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="variables">
|
||||
|
||||
<command name="set">
|
||||
<parameter type="tag" name="modifier" regex="(lower|upper)" occurrence="optional" />
|
||||
<parameter type="tag" name="modifier" regex="(lower|upper)first" occurrence="optional" />
|
||||
<parameter type="tag" name="modifier" regex="quotewildcard" occurrence="optional" />
|
||||
<parameter type="tag" name="modifier" regex="length" occurrence="optional" />
|
||||
<parameter type="string" name="name" regex="[[:alpha:]_][[:alnum:]_]*" />
|
||||
<parameter type="string" name="value" />
|
||||
</command>
|
||||
|
||||
<test name="string">
|
||||
<parameter type="matchtype" occurrence="optional" />
|
||||
<parameter type="comparator" occurrence="optional" />
|
||||
<parameter type="stringlist" name="source" />
|
||||
<parameter type="stringlist" name="key list" />
|
||||
</test>
|
||||
|
||||
</extension>
|
11
data/web/inc/lib/sieve/extensions/virustest.xml
Normal file
11
data/web/inc/lib/sieve/extensions/virustest.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="virustest">
|
||||
|
||||
<test name="virustest">
|
||||
<parameter type="comparator" occurrence="optional" />
|
||||
<parameter type="matchtype" occurrence="optional" />
|
||||
<parameter type="string" name="value" />
|
||||
</test>
|
||||
|
||||
</extension>
|
91
data/web/inc/lib/sieve/keywords.xml
Normal file
91
data/web/inc/lib/sieve/keywords.xml
Normal file
@@ -0,0 +1,91 @@
|
||||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<keywords>
|
||||
|
||||
<matchtype name="is" />
|
||||
<matchtype name="contains" />
|
||||
<matchtype name="matches" />
|
||||
<matchtype name="value">
|
||||
<parameter type="string" name="operator" regex="(gt|ge|eq|le|lt)" />
|
||||
</matchtype>
|
||||
|
||||
|
||||
<comparator name="i;octet" />
|
||||
<comparator name="i;ascii-casemap" />
|
||||
<comparator name="i;unicode-casemap" />
|
||||
|
||||
<addresspart name="all" />
|
||||
<addresspart name="localpart" />
|
||||
<addresspart name="domain" />
|
||||
|
||||
|
||||
<command name="discard" />
|
||||
|
||||
<command name="elsif">
|
||||
<parameter type="test" name="test command" />
|
||||
<parameter type="block" />
|
||||
</command>
|
||||
|
||||
<command name="else">
|
||||
<parameter type="block" />
|
||||
</command>
|
||||
|
||||
<command name="if">
|
||||
<parameter type="test" name="test command" />
|
||||
<parameter type="block" />
|
||||
</command>
|
||||
|
||||
<command name="keep" />
|
||||
|
||||
<command name="redirect">
|
||||
<parameter type="string" name="address string" />
|
||||
</command>
|
||||
|
||||
<command name="require">
|
||||
<parameter type="requirestrings" name="require string" />
|
||||
</command>
|
||||
|
||||
<command name="stop" />
|
||||
|
||||
|
||||
<test name="address">
|
||||
<parameter type="matchtype" occurrence="optional" />
|
||||
<parameter type="comparator" occurrence="optional" />
|
||||
<parameter type="addresspart" occurrence="optional" />
|
||||
<parameter type="stringlist" name="header list" />
|
||||
<parameter type="stringlist" name="key list" />
|
||||
</test>
|
||||
|
||||
<test name="allof">
|
||||
<parameter type="testlist" name="test" />
|
||||
</test>
|
||||
|
||||
<test name="anyof">
|
||||
<parameter type="testlist" name="test" />
|
||||
</test>
|
||||
|
||||
<test name="exists">
|
||||
<parameter type="stringlist" name="header names" />
|
||||
</test>
|
||||
|
||||
<test name="false" />
|
||||
|
||||
<test name="header">
|
||||
<parameter type="matchtype" occurrence="optional" />
|
||||
<parameter type="comparator" occurrence="optional" />
|
||||
<parameter type="stringlist" name="header names" />
|
||||
<parameter type="stringlist" name="key list" />
|
||||
</test>
|
||||
|
||||
<test name="not">
|
||||
<parameter type="test" />
|
||||
</test>
|
||||
|
||||
<test name="size">
|
||||
<parameter type="tag" regex="(over|under)" />
|
||||
<parameter type="number" name="limit" />
|
||||
</test>
|
||||
|
||||
<test name="true" />
|
||||
|
||||
</keywords>
|
Reference in New Issue
Block a user