Code

* Created "old" branch and moved stuff
[gosa.git] / branches / old / gosa-plugins / mail / personal / mail / sieve / class_semantics.inc
diff --git a/branches/old/gosa-plugins/mail/personal/mail/sieve/class_semantics.inc b/branches/old/gosa-plugins/mail/personal/mail/sieve/class_semantics.inc
new file mode 100644 (file)
index 0000000..1c00051
--- /dev/null
@@ -0,0 +1,613 @@
+<?php
+
+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|regex|imapflags|copy)';
+
+       function Semantics($command)
+       {
+               $this->command_ = $command;
+               $this->unknown = false;
+               switch ($command)
+               {
+
+               /********************
+                * control commands
+                */
+               case 'require':
+                       /* require <capabilities: string-list> */
+                       $this->s_ = array(
+                               '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_ .'"'),
+                                               array('occurrences' => '+', 'regex' => '"comparator-i;(octet|ascii-casemap|ascii-numeric)"')
+                                       ))
+                               )
+                       );
+                       break;
+
+               case 'if':
+                       /* if <test> <block> */
+                       $this->s_ = array(
+                               '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')
+                                       )),
+                                       array('class' => 'block-start', 'occurrences' => '1', 'values' => array(
+                                               array('occurrences' => '1', 'regex' => '{', 'name' => 'block')
+                                       ))
+                               )
+                       );
+                       break;
+
+               case 'elsif':
+                       /* elsif <test> <block> */
+                       $this->s_ = array(
+                               'valid_after' => '(if|elsif)',
+                               'arguments' => array(
+                                       array('class' => 'identifier', 'occurrences' => '1', 'values' => array(
+                                               array('occurrences' => '1', 'regex' => $this->testCommands_, 'name' => 'test')
+                                       )),
+                                       array('class' => 'block-start', 'occurrences' => '1', 'values' => array(
+                                               array('occurrences' => '1', 'regex' => '{', 'name' => 'block')
+                                       ))
+                               )
+                       );
+                       break;
+
+               case 'else':
+                       /* else <block> */
+                       $this->s_ = array(
+                               'valid_after' => '(if|elsif)',
+                               'arguments' => array(
+                                       array('class' => 'block-start', 'occurrences' => '1', 'values' => array(
+                                               array('occurrences' => '1', 'regex' => '{', 'name' => 'block')
+                                       ))
+                               )
+                       );
+                       break;
+
+
+               /*******************
+                * action commands
+                */
+               case 'discard':
+               case 'keep':
+               case 'stop':
+                       /* discard / keep / stop */
+                       $this->s_ = array(
+                               'valid_after' => str_replace('(', '(script-start|', $this->nonTestCommands_)
+                       );
+                       break;
+
+               case 'fileinto':
+                       /* fileinto [":copy"] <folder: string> */
+                       $this->s_ = array(
+                               'requires' => 'fileinto',
+                               '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 [":copy"] <address: string> */
+                       $this->s_ = array(
+                               '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')
+                                       ))
+                               )
+                       );
+                       break;
+
+               case 'reject':
+                       /* reject <reason: string> */
+                       $this->s_ = array(
+                               'requires' => 'reject',
+                               'valid_after' => $this->nonTestCommands_,
+                               'arguments' => array(
+                                       array('class' => 'string', 'occurrences' => '1', 'values' => array(
+                                               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')
+                                       ))
+                               )
+                       );
+                       break;
+
+               case 'vacation':
+                       /* vacation [":days" number] [":addresses" string-list] [":subject" string] [":mime"] <reason: string> */
+                       $this->s_ = array(
+                               'requires' => 'vacation',
+                               'valid_after' => $this->nonTestCommands_,
+                               'arguments' => array(
+                                       array('class' => 'tag', 'occurrences' => '*', 'values' => array(
+                                               array('occurrences' => '?', 'regex' => ':days', 'name' => 'days',
+                                                       'add' => array(
+                                                               array('class' => 'number', 'occurrences' => '1', 'values' => array(
+                                                                       array('occurrences' => '1', 'regex' => '.*', 'name' => 'period')
+                                                               ))
+                                                       )
+                                               ),
+                                               array('occurrences' => '?', 'regex' => ':addresses', 'name' => 'addresses',
+                                                       'add' => array(
+                                                               array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
+                                                                       array('occurrences' => '+', 'regex' => '".*"', 'name' => 'address')
+                                                               ))
+                                                       )
+                                               ),
+                                               array('occurrences' => '?', 'regex' => ':subject', 'name' => 'subject',
+                                                       'add' => array(
+                                                               array('class' => 'string', 'occurrences' => '1', 'values' => array(
+                                                                       array('occurrences' => '1', 'regex' => '".*"', 'name' => 'subject')
+                                                               ))
+                                                       )
+                                               ),
+                                               array('occurrences' => '?', 'regex' => ':mime', 'name' => 'mime')
+                                       )),
+                                       array('class' => 'string', 'occurrences' => '1', 'values' => array(
+                                               array('occurrences' => '1', 'regex' => '.*', 'name' => 'reason')
+                                       ))
+                               )
+                       );
+                       break;
+
+
+               /*****************
+                * test commands
+                */
+               case 'address':
+                       /* address [address-part: tag] [comparator: tag] [match-type: tag] <header-list: string-list> <key-list: string-list> */
+                       $this->s_ = array(
+                               'valid_after' => $this->testsValidAfter_,
+                               'arguments' => array(
+                                       array('class' => 'tag', 'occurrences' => '*', 'post-call' => 'checkTags_', 'values' => array(
+                                               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(
+                                                               array('class' => 'string', 'occurrences' => '1', 'call' => 'setComparator_', 'values' => array(
+                                                                       array('occurrences' => '1', 'regex' => '"i;(octet|ascii-casemap)"', 'name' => 'comparator-string'),
+                                                                       array('occurrences' => '1', 'regex' => '"i;ascii-numeric"', 'requires' => 'comparator-i;ascii-numeric', 'name' => 'comparator-string')
+                                                               ))
+                                                       )
+                                               )
+                                       )),
+                                       array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
+                                               array('occurrences' => '+', 'regex' => '".*"', 'name' => 'header')
+                                       )),
+                                       array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
+                                               array('occurrences' => '+', 'regex' => '".*"', 'name' => 'key')
+                                       ))
+                               )
+                       );
+                       break;
+
+               case 'allof':
+               case 'anyof':
+                       /* allof <tests: test-list>
+                          anyof <tests: test-list> */
+                       $this->s_ = array(
+                               'valid_after' => $this->testsValidAfter_,
+                               'arguments' => array(
+                                       array('class' => 'left-parant', 'occurrences' => '1', 'values' => array(
+                                               array('occurrences' => '1', 'regex' => '\(', 'name' => 'test-list')
+                                       )),
+                                       array('class' => 'identifier', 'occurrences' => '+', 'values' => array(
+                                               array('occurrences' => '+', 'regex' => $this->testCommands_, 'name' => 'test')
+                                       ))
+                               )
+                       );
+                       break;
+
+               case 'envelope':
+                       /* envelope [address-part: tag] [comparator: tag] [match-type: tag] <envelope-part: string-list> <key-list: string-list> */
+                       $this->s_ = array(
+                               'requires' => 'envelope',
+                               'valid_after' => $this->testsValidAfter_,
+                               'arguments' => array(
+                                       array('class' => 'tag', 'occurrences' => '*', 'post-call' => 'checkTags_', 'values' => array(
+                                               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(
+                                                               array('class' => 'string', 'occurrences' => '1', 'call' => 'setComparator_', 'values' => array(
+                                                                       array('occurrences' => '1', 'regex' => '"i;(octet|ascii-casemap)"', 'name' => 'comparator-string'),
+                                                                       array('occurrences' => '1', 'regex' => '"i;ascii-numeric"', 'requires' => 'comparator-i;ascii-numeric', 'name' => 'comparator-string')
+                                                               ))
+                                                       )
+                                               )
+                                       )),
+                                       array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
+                                               array('occurrences' => '+', 'regex' => '".*"', 'name' => 'envelope-part')
+                                       )),
+                                       array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
+                                               array('occurrences' => '+', 'regex' => '".*"', 'name' => 'key')
+                                       ))
+                               )
+                       );
+                       break;
+
+               case 'exists':
+                       /* exists <header-names: string-list> */
+                       $this->s_ = array(
+                               'valid_after' => $this->testsValidAfter_,
+                               'arguments' => array(
+                                       array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
+                                               array('occurrences' => '+', 'regex' => '".*"', 'name' => 'header')
+                                       ))
+                               )
+                       );
+                       break;
+
+               case 'header':
+                       /* header [comparator: tag] [match-type: tag] <header-names: string-list> <key-list: string-list> */
+                       $this->s_ = array(
+                               'valid_after' => $this->testsValidAfter_,
+                               'arguments' => array(
+                                       array('class' => 'tag', 'occurrences' => '*', 'post-call' => 'checkTags_', 'values' => array(
+                                               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(
+                                                                       array('occurrences' => '1', 'regex' => '"i;(octet|ascii-casemap)"', 'name' => 'comparator-string'),
+                                                                       array('occurrences' => '1', 'regex' => '"i;ascii-numeric"', 'requires' => 'comparator-i;ascii-numeric', 'name' => 'comparator-string')
+                                                               ))
+                                                       )
+                                               )
+                                       )),
+                                       array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
+                                               array('occurrences' => '+', 'regex' => '".*"', 'name' => 'header')
+                                       )),
+                                       array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
+                                               array('occurrences' => '+', 'regex' => '".*"', 'name' => 'key')
+                                       ))
+                               )
+                       );
+                       break;
+
+               case 'not':
+                       /* not <test> */
+                       $this->s_ = array(
+                               'valid_after' => $this->testsValidAfter_,
+                               'arguments' => array(
+                                       array('class' => 'identifier', 'occurrences' => '1', 'values' => array(
+                                               array('occurrences' => '1', 'regex' => $this->testCommands_, 'name' => 'test')
+                                       ))
+                               )
+                       );
+                       break;
+
+               case 'size':
+                       /* size <":over" / ":under"> <limit: number> */
+                       $this->s_ = array(
+                               'valid_after' => $this->testsValidAfter_,
+                               'arguments' => array(
+                                       array('class' => 'tag', 'occurrences' => '1', 'values' => array(
+                                               array('occurrences' => '1', 'regex' => ':(over|under)', 'name' => 'size-type')
+                                       )),
+                                       array('class' => 'number', 'occurrences' => '1', 'values' => array(
+                                               array('occurrences' => '1', 'regex' => '.*', 'name' => 'limit')
+                                       ))
+                               )
+                       );
+                       break;
+
+               case 'true':
+               case 'false':
+                       /* true / false */
+                       $this->s_ = array(
+                               'valid_after' => $this->testsValidAfter_
+                       );
+                       break;
+
+
+               /********************
+                * unknown commands
+                */
+               default:
+                       $this->unknown = true;
+               }
+       }
+
+       function setExtensionFuncs($setFn, $checkFn)
+       {
+               if (is_callable($setFn) && is_callable($checkFn))
+               {
+                       $this->registerExtensionFn_ = $setFn;
+                       $this->isExtensionRegisteredFn_ = $checkFn;
+               }
+       }
+
+       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')
+               {
+                       if (!$this->wasRequired_('relational'))
+                       {
+                               $this->message = 'missing require for match-type '. $text;
+                               return false;
+                       }
+
+                       array_unshift($this->s_['arguments'],
+                               array('class' => 'string', 'occurrences' => '1', 'values' => array(
+                                       array('occurrences' => '1', 'regex' => '"(lt|le|eq|ge|gt|ne)"', 'name' => 'relation-string'),
+                               ))
+                       );
+               }
+               // 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;
+       }
+
+       function setComparator_($text)
+       {
+               $this->comparator_ = $text;
+               return true;
+       }
+
+       function checkAddrPart_($text)
+       {
+               if ($text == ':user' || $text == ':detail')
+               {
+                       if (!$this->wasRequired_('subaddress'))
+                       {
+                               $this->message = 'missing require for tag '. $text;
+                               return false;
+                       }
+               }
+               return true;
+       }
+
+       function checkTags_()
+       {
+               if (isset($this->matchType_) &&
+                   $this->matchType_ == ':count' &&
+                   $this->comparator_ != '"i;ascii-numeric"')
+               {
+                       $this->message = 'match-type :count needs comparator i;ascii-numeric';
+                       return false;
+               }
+               return true;
+       }
+
+       function validCommand($prev, $line)
+       {
+               // 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)
+       {
+               // Check if command expects any arguments
+               if (!isset($this->s_['arguments']))
+               {
+                       $this->message = $id .' where semicolon expected';
+                       return false;
+               }
+
+               foreach ($this->s_['arguments'] as $arg)
+               {
+                       if ($class == $arg['class'])
+                       {
+                               return true;
+                       }
+
+                       // Is the argument required
+                       if ($arg['occurrences'] != '?' && $arg['occurrences'] != '*')
+                       {
+                               $this->message = $id .' where '. $arg['class'] .' expected';
+                               return false;
+                       }
+
+                       if (isset($arg['post-call']) &&
+                               !call_user_func(array(&$this, $arg['post-call'])))
+                       {
+                               return false;
+                       }
+                       array_shift($this->s_['arguments']);
+               }
+
+               $this->message = 'unexpected '. $id;
+               return false;
+       }
+
+       function startStringList($line)
+       {
+               if (!$this->validClass_('string', 'string'))
+               {
+                       $this->message = 'line '. $line .': '. $this->message;
+                       return false;
+               }
+               else if (!isset($this->s_['arguments'][0]['list']))
+               {
+                       $this->message = 'line '. $line .': '. 'left bracket where '. $this->s_['arguments'][0]['class'] .' expected';
+                       return false;
+               }
+
+               $this->s_['arguments'][0]['occurrences'] = '+';
+               return true;
+       }
+
+       function endStringList()
+       {
+               array_shift($this->s_['arguments']);
+       }
+
+       function validToken($class, &$text, &$line)
+       {
+               $name = $class . ($class != $text ? " $text" : '');
+
+               // Make sure the argument has a valid class
+               if (!$this->validClass_($class, $name))
+               {
+                       $this->message = 'line '. $line .': '. $this->message;
+                       return false;
+               }
+
+               $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']) && !$this->wasRequired_($val['requires']))
+                               {
+                                       $this->message = 'line '. $line .': missing require for '. $val['name'] .' '. $text;
+                                       return false;
+                               }
+
+                               // Check if a possible value of this argument may occur
+                               if ($val['occurrences'] == '?' || $val['occurrences'] == '1')
+                               {
+                                       $val['occurrences'] = '0';
+                               }
+                               else if ($val['occurrences'] == '+')
+                               {
+                                       $val['occurrences'] = '*';
+                               }
+                               else if ($val['occurrences'] == '0')
+                               {
+                                       $this->message = 'line '. $line .': too many '. $val['name'] .' '. $class .'s near '. $text;
+                                       return false;
+                               }
+
+                               // Call extra processing function if defined
+                               if (isset($val['call']) && !call_user_func(array(&$this, $val['call']), $text) ||
+                                       isset($arg['call']) && !call_user_func(array(&$this, $arg['call']), $text))
+                               {
+                                       $this->message = 'line '. $line .': '. $this->message;
+                                       return false;
+                               }
+
+                               // Set occurrences appropriately
+                               if ($arg['occurrences'] == '?' || $arg['occurrences'] == '1')
+                               {
+                                       array_shift($this->s_['arguments']);
+                               }
+                               else
+                               {
+                                       $arg['occurrences'] = '*';
+                               }
+
+                               // Add argument(s) expected to follow right after this one
+                               if (isset($val['add']))
+                               {
+                                       while ($add_arg = array_pop($val['add']))
+                                       {
+                                               array_unshift($this->s_['arguments'], $add_arg);
+                                       }
+                               }
+
+                               return true;
+                       }
+               }
+
+               $this->message = 'line '. $line .': unexpected '. $name;
+               return false;
+       }
+
+       function done($class, $text, $line)
+       {
+               if (isset($this->s_['arguments']))
+               {
+                       foreach ($this->s_['arguments'] as $arg)
+                       {
+                               if ($arg['occurrences'] == '+' || $arg['occurrences'] == '1')
+                               {
+                                       $this->message = 'line '. $line .': '. $class .' '. $text .' where '. $arg['class'] .' expected';
+                                       return false;
+                               }
+                       }
+               }
+               return true;
+       }
+}
+
+?>