Code

Updated sieve templates
[gosa.git] / include / sieve / class_semantics.inc
index e37d5c6313541234afa95c7d207867dbcef42ea4..1c00051c052bb1f477b3eead6c19f9433e7ce445 100644 (file)
@@ -1,22 +1,24 @@
 <?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)
                {
@@ -27,7 +29,7 @@ class Semantics
                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_ .'"'),
@@ -40,8 +42,7 @@ class Semantics
                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')
@@ -56,7 +57,7 @@ class Semantics
                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')
@@ -71,7 +72,7 @@ class Semantics
                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')
@@ -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 <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')
                                        ))
@@ -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 <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')
                                        ))
@@ -123,10 +138,27 @@ class Semantics
                        /* 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')
                                        ))
                                )
                        );
@@ -136,7 +168,7 @@ class Semantics
                        /* 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',
@@ -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] <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(
@@ -205,7 +237,7 @@ class Semantics
                        /* 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')
@@ -221,10 +253,10 @@ class Semantics
                        /* 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(
@@ -248,7 +280,7 @@ class Semantics
                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')
@@ -260,10 +292,10 @@ class Semantics
                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(
@@ -286,7 +318,7 @@ class Semantics
                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')
@@ -298,7 +330,7 @@ class Semantics
                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')
@@ -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 = 'missing require for match-type '. $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 = 'missing require for tag '. $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)
@@ -459,16 +524,6 @@ class Semantics
        {
                $name = $class . ($class != $text ? " $text" : '');
 
-               // Check if the command needs to be required
-               // TODO: move this to somewhere more appropriate
-               global $requires_;
-               if (isset($this->s_['requires']) &&
-                   !in_array('"'.$this->s_['requires'].'"', $requires_))
-               {
-                       $this->message = 'line '. $line .': missing require for '. $this->command_;
-                       return false;
-               }
-
                // Make sure the argument has a valid class
                if (!$this->validClass_($class, $name))
                {
@@ -482,8 +537,7 @@ class Semantics
                        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 = 'line '. $line .': missing require for '. $val['name'] .' '. $text;
                                        return false;