summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: 2fb4859)
raw | patch | inline | side by side (parent: 2fb4859)
author | hickert <hickert@594d385d-05f5-0310-b6e9-bd551577e9d8> | |
Thu, 22 Mar 2007 10:33:14 +0000 (10:33 +0000) | ||
committer | hickert <hickert@594d385d-05f5-0310-b6e9-bd551577e9d8> | |
Thu, 22 Mar 2007 10:33:14 +0000 (10:33 +0000) |
The author has updated his sieve classes.
git-svn-id: https://oss.gonicus.de/repositories/gosa/trunk@5859 594d385d-05f5-0310-b6e9-bd551577e9d8
git-svn-id: https://oss.gonicus.de/repositories/gosa/trunk@5859 594d385d-05f5-0310-b6e9-bd551577e9d8
include/sieve/class_My_Parser.inc | patch | blob | history | |
include/sieve/class_My_Scanner.inc | [new file with mode: 0644] | patch | blob |
include/sieve/class_parser.inc | patch | blob | history | |
include/sieve/class_scanner.inc | patch | blob | history | |
include/sieve/class_semantics.inc | patch | blob | history |
index 34d5de1215c3f01ed93422854527398510f20d57..197d94ab2922e6a44032f344e1249c3d5cebcb32 100644 (file)
{
var $parent = NULL;
+ var $registeredExtensions_ =array();
+
function My_Parser($parent)
{
+ $this->registeredExtensions_ = array();
$this->parent = $parent;
}
*/
function parse($script)
{
+ $this->registeredExtensions_ = array();
$this->status_text = "incomplete";
$this->script_ = $script;
$this->tree_ = new My_Tree(@Scanner::scriptStart(),$this);
$this->tree_->setDumpFunc(array(&$this, 'dumpToken_'));
- $this->scanner_ = new Scanner($this->script_);
+ $this->scanner_ = new My_Scanner($this->script_);
$this->scanner_->setCommentFunc(array($this, 'comment_'));
if ($this->commands_($this->tree_->getRoot()) &&
diff --git a/include/sieve/class_My_Scanner.inc b/include/sieve/class_My_Scanner.inc
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+
+class My_Scanner extends Scanner
+{
+ function tokenize(&$script)
+ {
+ $pos = 0;
+ $line = 1;
+ $script_length = mb_strlen($script);
+
+ while ($pos < $script_length)
+ {
+ foreach ($this->token_match_ as $class => $regex)
+ {
+ if (preg_match('/^'. $regex .'/', mb_substr($script, $pos), $match))
+ {
+ $length = mb_strlen($match[0]);
+
+ if ($class != 'whitespace')
+ {
+ array_push($this->tokens_, array(
+ 'class' => $class,
+ 'text' => chop(mb_substr($script, $pos, $length)),
+ 'line' => $line,
+ ));
+ }
+ if ($class == 'unknown')
+ {
+ return;
+ }
+
+ $pos += $length;
+ $line += mb_substr_count($match[0], "\n");
+ break;
+ }
+ }
+ }
+ array_push($this->tokens_, array(
+ 'class' => 'script-end',
+ 'text' => 'script-end',
+ 'line' => $line,
+ ));
+ }
+
+ var $commentFn_ = null;
+ var $tokenPos_ = 0;
+ var $tokens_ = array();
+ var $token_match_ = array (
+ 'left-bracket' => '\[',
+ 'right-bracket' => '\]',
+ 'block-start' => '\{',
+ 'block-end' => '\}',
+ 'left-parant' => '\(',
+ 'right-parant' => '\)',
+ 'comma' => ',',
+ 'semicolon' => ';',
+ 'whitespace' => '[ \r\n\t]+',
+ 'tag' => ':[[:alpha:]_][[:alnum:]_]*(?=\b)',
+ 'quoted-string' => '"(?:\\[\\"]|[^\x00"])*"',
+ 'number' => '[[:digit:]]+(?:[KMG])?(?=\b)',
+ 'comment' => '(?:\/\*(?:[^\*]|\*(?=[^\/]))*\*\/|#[^\r\n]*\r?\n)',
+# 'multi-line' => 'text:[ \t]*(?:#[^\r\n]*)?\r?\n(\.[^\r\n]+\r?\n|[^\.]*\r?\n)*\.\r?\n',
+ 'multi-line' => 'text:[^;]*',
+ 'identifier' => '[[:alpha:]_][[:alnum:]_]*(?=\b)',
+ 'unknown token' => '[^ \r\n\t]+'
+ );
+}
+
+?>
index 933232ecab081da86ff37986a0f662a2153f58e9..345895231f2ec801cd55107e00153f68ec004406 100644 (file)
<?php
+
+#include_once 'class.tree.php';
+#include_once 'class.scanner.php';
+#include_once 'class.semantics.php';
+
class Parser
{
var $scanner_;
var $script_;
var $tree_;
var $status_;
+ var $registeredExtensions_;
var $status_text;
function parse($script)
{
+ $this->registeredExtensions_ = array();
$this->status_text = "incomplete";
$this->script_ = $script;
return strval($token);
}
+ function getPrevTokenText_($parent_id)
+ {
+ $childs = $this->tree_->getChilds($parent_id);
+
+ for ($i=count($childs); $i>0; --$i)
+ {
+ $prev = $this->tree_->getNode($childs[$i-1]);
+
+ if (in_array($prev['text'], array('{', '(', ',')))
+ {
+ // use command owning a block or list
+ $prev = $this->tree_->getNode($parent_id);
+ }
+
+ if ($prev['class'] != 'comment')
+ {
+ return $prev['text'];
+ }
+ }
+
+ $prev = $this->tree_->getNode($parent_id);
+ return $prev['text'];
+ }
+
+ function getSemantics_($token_text)
+ {
+ $semantics = new Semantics($token_text);
+ $semantics->setExtensionFuncs(array(&$this, 'registerExtension_'), array(&$this, 'isExtensionRegistered_'));
+ return $semantics;
+ }
+
+ function registerExtension_($extension)
+ {
+ array_push($this->registeredExtensions_, str_replace('"', '', $extension));
+ }
+
+ function isExtensionRegistered_($extension)
+ {
+ return (in_array($extension, $this->registeredExtensions_) ? true : false);
+ }
+
function success_($text = null)
{
if ($text != null)
return false;
}
+ /*******************************************************************************
+ * methods for recursive descent start below
+ */
+
function comment_($token)
{
$this->tree_->addChild($token);
// Get and check a command token
$token = $this->scanner_->nextToken();
- $semantics = new Semantics($token['text']);
- if ($semantics->unknown)
+ $semantics = $this->getSemantics_($token['text']);
+ if (!$semantics->validCommand($this->getPrevTokenText_($parent_id), $token['line']))
{
- return $this->error_('unknown command: '. $token['text']);
- }
-
- $last = $this->tree_->getLastNode($parent_id);
- if (!$semantics->validAfter($last['text']))
- {
- #return $this->error_('"'. $token['text'] .'" may not appear after "'. $last['text'] .'"');
+ return $this->error_($semantics->message);
}
// Process eventual arguments
}
// Get semantics for this test command
- $this_semantics = new Semantics($token['text']);
- if ($this_semantics->unknown)
+ $this_semantics = $this->getSemantics_($token['text']);
+ if (!$this_semantics->validCommand($this->getPrevTokenText_($parent_id), $token['line']))
{
- return $this->error_('unknown test: '. $token['text']);
+ return $this->error_($this_semantics->message);
}
$this_node = $this->tree_->addChildTo($parent_id, $token);
index 2a7fc37d2e77a0b7aec019bc1cdeeb4293a679d3..3e22bb1d0ee8c6a3a2aae7b6662fa35da5e54a50 100644 (file)
}
}
}
+
array_push($this->tokens_, array(
'class' => 'script-end',
'text' => 'script-end',
'quoted-string' => '"(?:\\[\\"]|[^\x00"])*"',
'number' => '[[:digit:]]+(?:[KMG])?(?=\b)',
'comment' => '(?:\/\*(?:[^\*]|\*(?=[^\/]))*\*\/|#[^\r\n]*\r?\n)',
-# 'multi-line' => 'text:[ \t]*(?:#[^\r\n]*)?\r?\n(\.[^\r\n]+\r?\n|[^\.]*\r?\n)*\.\r?\n',
- 'multi-line' => 'text:[^;]*',
+ 'multi-line' => 'text:[ \t]*(?:#[^\r\n]*)?\r?\n(\.[^\r\n]+\r?\n|[^\.]*\r?\n)*\.\r?\n',
'identifier' => '[[:alpha:]_][[:alnum:]_]*(?=\b)',
'unknown token' => '[^ \r\n\t]+'
);
}
-?>
+?>
\ No newline at end of file
index 77b27ccacbd5d9397e04074eca33c39b6beac538..173c5cfb736c43b9cb00ba50fdc43e91c076a415 100644 (file)
<?php
-$requires_ = array();
-
class Semantics
{
+ var $registerExtensionFn_;
+ var $isExtensionRegisteredFn_;
+
var $command_;
var $comparator_;
var $matchType_;
var $s_;
var $unknown;
var $message;
+ var $nonTestCommands_ = '(require|if|elsif|else|reject|fileinto|redirect|stop|keep|discard|mark|unmark|setflag|addflag|removeflag)';
+ var $testsValidAfter_ = '(if|elsif|anyof|allof|not)';
var $testCommands_ = '(address|envelope|header|size|allof|anyof|exists|not|true|false)';
- var $requireStrings_ = '(envelope|fileinto|reject|vacation|relational|subaddress)';
+ var $requireStrings_ = '(envelope|fileinto|reject|vacation|relational|subaddress|regex|imapflags|copy)';
function Semantics($command)
{
$this->command_ = $command;
-
$this->unknown = false;
switch ($command)
{
case 'require':
/* require <capabilities: string-list> */
$this->s_ = array(
- 'valid_after' => array('script-start', 'require'),
+ 'valid_after' => '(script-start|require)',
'arguments' => array(
array('class' => 'string', 'list' => true, 'name' => 'require-string', 'occurrences' => '1', 'call' => 'setRequire_', 'values' => array(
array('occurrences' => '+', 'regex' => '"'. $this->requireStrings_ .'"'),
case 'if':
/* if <test> <block> */
$this->s_ = array(
- 'valid_after' => array('script-start', 'require', 'if', 'elsif', 'else',
- 'reject', 'fileinto', 'redirect', 'stop', 'keep', 'discard'),
+ 'valid_after' => str_replace('(', '(script-start|', $this->nonTestCommands_),
'arguments' => array(
array('class' => 'identifier', 'occurrences' => '1', 'values' => array(
array('occurrences' => '1', 'regex' => $this->testCommands_, 'name' => 'test')
case 'elsif':
/* elsif <test> <block> */
$this->s_ = array(
- 'valid_after' => array('if', 'elsif'),
+ 'valid_after' => '(if|elsif)',
'arguments' => array(
array('class' => 'identifier', 'occurrences' => '1', 'values' => array(
array('occurrences' => '1', 'regex' => $this->testCommands_, 'name' => 'test')
case 'else':
/* else <block> */
$this->s_ = array(
- 'valid_after' => array('if', 'elsif'),
+ 'valid_after' => '(if|elsif)',
'arguments' => array(
array('class' => 'block-start', 'occurrences' => '1', 'values' => array(
array('occurrences' => '1', 'regex' => '{', 'name' => 'block')
/*******************
* action commands
*/
+ case 'discard':
case 'keep':
case 'stop':
- case 'discard':
- /* keep / stop / discard */
+ /* discard / keep / stop */
$this->s_ = array(
- 'valid_after' => array('script-start', 'require', 'if', 'elsif', 'else',
- 'reject', 'fileinto', 'redirect', 'stop', 'keep', 'discard')
+ 'valid_after' => str_replace('(', '(script-start|', $this->nonTestCommands_)
);
break;
case 'fileinto':
- /* fileinto <folder: string> */
+ /* fileinto [":copy"] <folder: string> */
$this->s_ = array(
'requires' => 'fileinto',
- 'valid_after' => array('require', 'if', 'elsif', 'else', 'reject', 'fileinto', 'redirect', 'stop', 'keep', 'discard'),
+ 'valid_after' => $this->nonTestCommands_,
'arguments' => array(
+ array('class' => 'tag', 'occurrences' => '?', 'values' => array(
+ array('occurrences' => '?', 'regex' => ':copy', 'requires' => 'copy', 'name' => 'copy')
+ )),
array('class' => 'string', 'occurrences' => '1', 'values' => array(
array('occurrences' => '1', 'regex' => '".*"', 'name' => 'folder')
))
);
break;
+ case 'mark':
+ case 'unmark':
+ /* mark / unmark */
+ $this->s_ = array(
+ 'requires' => 'imapflags',
+ 'valid_after' => $this->nonTestCommands_
+ );
+ break;
+
case 'redirect':
- /* redirect <address: string> */
+ /* redirect [":copy"] <address: string> */
$this->s_ = array(
- 'valid_after' => array('script-start', 'require', 'if', 'elsif', 'else', 'reject', 'fileinto', 'redirect', 'stop', 'keep', 'discard'),
+ 'valid_after' => str_replace('(', '(script-start|', $this->nonTestCommands_),
'arguments' => array(
+ array('class' => 'tag', 'occurrences' => '?', 'values' => array(
+ array('occurrences' => '?', 'regex' => ':copy', 'requires' => 'copy', 'name' => 'size-type')
+ )),
array('class' => 'string', 'occurrences' => '1', 'values' => array(
array('occurrences' => '1', 'regex' => '".*"', 'name' => 'address')
))
/* reject <reason: string> */
$this->s_ = array(
'requires' => 'reject',
- 'valid_after' => array('require', 'if', 'elsif', 'else', 'reject', 'fileinto', 'redirect', 'stop', 'keep', 'discard'),
+ 'valid_after' => $this->nonTestCommands_,
'arguments' => array(
array('class' => 'string', 'occurrences' => '1', 'values' => array(
- array('occurrences' => '1', 'regex' => '.*', 'name' => 'reason')
+ array('occurrences' => '1', 'regex' => '".*"', 'name' => 'reason')
+ ))
+ )
+ );
+ break;
+
+ case 'setflag':
+ case 'addflag':
+ case 'removeflag':
+ /* setflag <flag-list: string-list> */
+ /* addflag <flag-list: string-list> */
+ /* removeflag <flag-list: string-list> */
+ $this->s_ = array(
+ 'requires' => 'imapflags',
+ 'valid_after' =>$this->nonTestCommands_,
+ 'arguments' => array(
+ array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
+ array('occurrences' => '+', 'regex' => '".*"', 'name' => 'key')
))
)
);
/* vacation [":days" number] [":addresses" string-list] [":subject" string] [":mime"] <reason: string> */
$this->s_ = array(
'requires' => 'vacation',
- 'valid_after' => array('require', 'if', 'elsif', 'else', 'reject', 'fileinto', 'redirect', 'stop', 'keep', 'discard'),
+ 'valid_after' => $this->nonTestCommands_,
'arguments' => array(
array('class' => 'tag', 'occurrences' => '*', 'values' => array(
array('occurrences' => '?', 'regex' => ':days', 'name' => 'days',
array('occurrences' => '?', 'regex' => ':mime', 'name' => 'mime')
)),
array('class' => 'string', 'occurrences' => '1', 'values' => array(
- array('occurrences' => '1', 'regex' => '.*', 'name' => 'reason')
+ array('occurrences' => '1', 'regex' => '".*"', 'name' => 'reason')
))
)
);
case 'address':
/* address [address-part: tag] [comparator: tag] [match-type: tag] <header-list: string-list> <key-list: string-list> */
$this->s_ = array(
- 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'),
+ 'valid_after' => $this->testsValidAfter_,
'arguments' => array(
array('class' => 'tag', 'occurrences' => '*', 'post-call' => 'checkTags_', 'values' => array(
- array('occurrences' => '?', 'regex' => ':(is|contains|matches|count|value)', 'call' => 'setMatchType_', 'name' => 'match-type'),
+ array('occurrences' => '?', 'regex' => ':(is|contains|matches|count|value|regex)', 'call' => 'setMatchType_', 'name' => 'match-type'),
array('occurrences' => '?', 'regex' => ':(all|localpart|domain|user|detail)', 'call' => 'checkAddrPart_', 'name' => 'address-part'),
array('occurrences' => '?', 'regex' => ':comparator', 'name' => 'comparator',
'add' => array(
/* allof <tests: test-list>
anyof <tests: test-list> */
$this->s_ = array(
- 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'),
+ 'valid_after' => $this->testsValidAfter_,
'arguments' => array(
array('class' => 'left-parant', 'occurrences' => '1', 'values' => array(
array('occurrences' => '1', 'regex' => '\(', 'name' => 'test-list')
/* envelope [address-part: tag] [comparator: tag] [match-type: tag] <envelope-part: string-list> <key-list: string-list> */
$this->s_ = array(
'requires' => 'envelope',
- 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'),
+ 'valid_after' => $this->testsValidAfter_,
'arguments' => array(
array('class' => 'tag', 'occurrences' => '*', 'post-call' => 'checkTags_', 'values' => array(
- array('occurrences' => '?', 'regex' => ':(is|contains|matches|count|value)', 'call' => 'setMatchType_', 'name' => 'match-type'),
+ array('occurrences' => '?', 'regex' => ':(is|contains|matches|count|value|regex)', 'call' => 'setMatchType_', 'name' => 'match-type'),
array('occurrences' => '?', 'regex' => ':(all|localpart|domain|user|detail)', 'call' => 'checkAddrPart_', 'name' => 'address-part'),
array('occurrences' => '?', 'regex' => ':comparator', 'name' => 'comparator',
'add' => array(
case 'exists':
/* exists <header-names: string-list> */
$this->s_ = array(
- 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'),
+ 'valid_after' => $this->testsValidAfter_,
'arguments' => array(
array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
array('occurrences' => '+', 'regex' => '".*"', 'name' => 'header')
case 'header':
/* header [comparator: tag] [match-type: tag] <header-names: string-list> <key-list: string-list> */
$this->s_ = array(
- 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'),
+ 'valid_after' => $this->testsValidAfter_,
'arguments' => array(
array('class' => 'tag', 'occurrences' => '*', 'post-call' => 'checkTags_', 'values' => array(
- array('occurrences' => '?', 'regex' => ':(is|contains|matches|count|value)', 'call' => 'setMatchType_', 'name' => 'match-type'),
+ array('occurrences' => '?', 'regex' => ':(is|contains|matches|count|value|regex)', 'call' => 'setMatchType_', 'name' => 'match-type'),
array('occurrences' => '?', 'regex' => ':comparator', 'name' => 'comparator',
'add' => array(
array('class' => 'string', 'occurrences' => '1', 'call' => 'setComparator_', 'values' => array(
case 'not':
/* not <test> */
$this->s_ = array(
- 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'),
+ 'valid_after' => $this->testsValidAfter_,
'arguments' => array(
array('class' => 'identifier', 'occurrences' => '1', 'values' => array(
array('occurrences' => '1', 'regex' => $this->testCommands_, 'name' => 'test')
case 'size':
/* size <":over" / ":under"> <limit: number> */
$this->s_ = array(
- 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'),
+ 'valid_after' => $this->testsValidAfter_,
'arguments' => array(
array('class' => 'tag', 'occurrences' => '1', 'values' => array(
array('occurrences' => '1', 'regex' => ':(over|under)', 'name' => 'size-type')
case 'false':
/* true / false */
$this->s_ = array(
- 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not')
+ 'valid_after' => $this->testsValidAfter_
);
break;
}
}
- function setRequire_($text)
+ function setExtensionFuncs($setFn, $checkFn)
{
- global $requires_;
-
- if(!is_array($requires_)){
- $requires_ = array();
+ if (is_callable($setFn) && is_callable($checkFn))
+ {
+ $this->registerExtensionFn_ = $setFn;
+ $this->isExtensionRegisteredFn_ = $checkFn;
}
+ }
- array_push($requires_, $text);
+ function setRequire_($extension)
+ {
+ call_user_func($this->registerExtensionFn_, $extension);
return true;
}
+ function wasRequired_($extension)
+ {
+ return call_user_func($this->isExtensionRegisteredFn_, $extension);
+ }
+
function setMatchType_($text)
{
// Do special processing for relational test extension
if ($text == ':count' || $text == ':value')
{
- global $requires_;
- if (!in_array('"relational"', $requires_))
+ if (!$this->wasRequired_('relational'))
{
$this->message = sprintf(_("Missing require statement for '%s' object."), $text);
return false;
))
);
}
+ // Do special processing for regex match-type extension
+ else if ($text == ':regex' && !$this->wasRequired_('regex'))
+ {
+ $this->message = 'missing require for match-type '. $text;
+ return false;
+ }
$this->matchType_ = $text;
return true;
}
{
if ($text == ':user' || $text == ':detail')
{
- global $requires_;
- if (!in_array('"subaddress"', $requires_))
+ if (!$this->wasRequired_('subaddress'))
{
$this->message = sprintf(_("Missing require statement for '%s' object."), $text);
return false;
return true;
}
- function validAfter($prev)
+ function validCommand($prev, $line)
{
- return in_array($prev, $this->s_['valid_after']);
+ // Check if command is known
+ if ($this->unknown)
+ {
+ $this->message = 'line '. $line .': unknown command "'. $this->command_ .'"';
+ return false;
+ }
+
+ // Check if the command needs to be required
+ if (isset($this->s_['requires']) && !$this->wasRequired_($this->s_['requires']))
+ {
+ $this->message = 'line '. $line .': missing require for command "'. $this->command_ .'"';
+ return false;
+ }
+
+ // Check if command may appear here
+ if (!ereg($this->s_['valid_after'], $prev))
+ {
+ $this->message = 'line '. $line .': "'. $this->command_ .'" may not appear after "'. $prev .'"';
+ return false;
+ }
+
+ return true;
}
function validClass_($class, $id)
function validToken($class, &$text, &$line)
{
- global $requires_;
-
- if(!is_array($requires_)){
- $requires_ = array();
- }
-
$name = $class . ($class != $text ? " $text" : '');
+<<<<<<< .mine
+=======
// Check if the command needs to be required
// TODO: move this to somewhere more appropriate
if (isset($this->s_['requires']) &&
return false;
}
+>>>>>>> .r5858
// Make sure the argument has a valid class
if (!$this->validClass_($class, $name))
{
$arg = &$this->s_['arguments'][0];
foreach ($arg['values'] as $val)
{
-
if (preg_match('/^'. $val['regex'] .'$/m', $text))
{
// Check if the argument value needs a 'require'
- if (isset($val['requires']) &&
- !in_array('"'.$val['requires'].'"', $requires_))
+ if (isset($val['requires']) && !$this->wasRequired_($val['requires']))
{
$this->message = sprintf(_("Error in line %s"),$line)." :".sprintf(_("Missing require statement for '%s' object."), $val['name']);
return false;
}
}
-?>
+?>
\ No newline at end of file