From 2c634b051943421f7406293ba4da4271d0f1c84f Mon Sep 17 00:00:00 2001 From: hickert Date: Thu, 22 Mar 2007 10:33:14 +0000 Subject: [PATCH] Updated sieve classes. The author has updated his sieve classes. git-svn-id: https://oss.gonicus.de/repositories/gosa/trunk@5859 594d385d-05f5-0310-b6e9-bd551577e9d8 --- include/sieve/class_My_Parser.inc | 6 +- include/sieve/class_My_Scanner.inc | 69 ++++++++++++ include/sieve/class_parser.inc | 70 +++++++++--- include/sieve/class_scanner.inc | 6 +- include/sieve/class_semantics.inc | 168 +++++++++++++++++++---------- 5 files changed, 249 insertions(+), 70 deletions(-) create mode 100644 include/sieve/class_My_Scanner.inc diff --git a/include/sieve/class_My_Parser.inc b/include/sieve/class_My_Parser.inc index 34d5de121..197d94ab2 100644 --- a/include/sieve/class_My_Parser.inc +++ b/include/sieve/class_My_Parser.inc @@ -9,8 +9,11 @@ class My_Parser extends Parser { var $parent = NULL; + var $registeredExtensions_ =array(); + function My_Parser($parent) { + $this->registeredExtensions_ = array(); $this->parent = $parent; } @@ -33,11 +36,12 @@ class My_Parser extends Parser */ 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 new file mode 100644 index 000000000..878927a5f --- /dev/null +++ b/include/sieve/class_My_Scanner.inc @@ -0,0 +1,69 @@ +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]+' + ); +} + +?> diff --git a/include/sieve/class_parser.inc b/include/sieve/class_parser.inc index 933232eca..345895231 100644 --- a/include/sieve/class_parser.inc +++ b/include/sieve/class_parser.inc @@ -1,15 +1,22 @@ registeredExtensions_ = array(); $this->status_text = "incomplete"; $this->script_ = $script; @@ -47,6 +54,47 @@ class Parser 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) @@ -74,6 +122,10 @@ class Parser return false; } + /******************************************************************************* + * methods for recursive descent start below + */ + function comment_($token) { $this->tree_->addChild($token); @@ -100,16 +152,10 @@ class Parser // 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 @@ -274,10 +320,10 @@ class Parser } // 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); diff --git a/include/sieve/class_scanner.inc b/include/sieve/class_scanner.inc index 2a7fc37d2..3e22bb1d0 100644 --- a/include/sieve/class_scanner.inc +++ b/include/sieve/class_scanner.inc @@ -58,6 +58,7 @@ class Scanner } } } + array_push($this->tokens_, array( 'class' => 'script-end', 'text' => 'script-end', @@ -130,11 +131,10 @@ class Scanner '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 diff --git a/include/sieve/class_semantics.inc b/include/sieve/class_semantics.inc index 77b27ccac..173c5cfb7 100644 --- a/include/sieve/class_semantics.inc +++ b/include/sieve/class_semantics.inc @@ -1,22 +1,24 @@ command_ = $command; - $this->unknown = false; switch ($command) { @@ -27,7 +29,7 @@ class Semantics case 'require': /* require */ $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_ .'"'), @@ -40,8 +42,7 @@ class Semantics case 'if': /* if */ $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') @@ -56,7 +57,7 @@ class Semantics case 'elsif': /* elsif */ $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') @@ -71,7 +72,7 @@ class Semantics case 'else': /* else */ $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') @@ -84,22 +85,24 @@ class Semantics /******************* * 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 */ + /* fileinto [":copy"] */ $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') )) @@ -107,11 +110,23 @@ class Semantics ); break; + case 'mark': + case 'unmark': + /* mark / unmark */ + $this->s_ = array( + 'requires' => 'imapflags', + 'valid_after' => $this->nonTestCommands_ + ); + break; + case 'redirect': - /* redirect */ + /* redirect [":copy"] */ $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') )) @@ -123,10 +138,27 @@ class Semantics /* reject */ $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 */ + /* addflag */ + /* removeflag */ + $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') )) ) ); @@ -136,7 +168,7 @@ class Semantics /* vacation [":days" number] [":addresses" string-list] [":subject" string] [":mime"] */ $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', @@ -163,7 +195,7 @@ class Semantics 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') )) ) ); @@ -176,10 +208,10 @@ class Semantics case 'address': /* address [address-part: tag] [comparator: tag] [match-type: tag] */ $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( @@ -205,7 +237,7 @@ class Semantics /* allof anyof */ $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') @@ -221,10 +253,10 @@ class Semantics /* envelope [address-part: tag] [comparator: tag] [match-type: tag] */ $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( @@ -248,7 +280,7 @@ class Semantics case 'exists': /* exists */ $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') @@ -260,10 +292,10 @@ class Semantics case 'header': /* header [comparator: tag] [match-type: tag] */ $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( @@ -286,7 +318,7 @@ class Semantics case 'not': /* not */ $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') @@ -298,7 +330,7 @@ class Semantics case 'size': /* size <":over" / ":under"> */ $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') @@ -314,7 +346,7 @@ class Semantics case 'false': /* true / false */ $this->s_ = array( - 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not') + 'valid_after' => $this->testsValidAfter_ ); break; @@ -327,25 +359,32 @@ class Semantics } } - 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; @@ -357,6 +396,12 @@ class Semantics )) ); } + // 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; } @@ -371,8 +416,7 @@ class Semantics { 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; @@ -393,9 +437,30 @@ class Semantics 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) @@ -457,14 +522,10 @@ class Semantics 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']) && @@ -474,6 +535,7 @@ class Semantics return false; } +>>>>>>> .r5858 // Make sure the argument has a valid class if (!$this->validClass_($class, $name)) { @@ -484,12 +546,10 @@ class Semantics $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; @@ -562,4 +622,4 @@ class Semantics } } -?> +?> \ No newline at end of file -- 2.30.2