| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611 | 
							- <?php namespace Sieve;
 
- require_once('SieveKeywordRegistry.php');
 
- require_once('SieveToken.php');
 
- require_once('SieveException.php');
 
- class SieveSemantics
 
- {
 
-     protected static $requiredExtensions_ = array();
 
-     protected $comparator_;
 
-     protected $matchType_;
 
-     protected $addressPart_;
 
-     protected $tags_ = array();
 
-     protected $arguments_;
 
-     protected $deps_ = array();
 
-     protected $followupToken_;
 
-     public function __construct($token, $prevToken)
 
-     {
 
-         $this->registry_ = SieveKeywordRegistry::get();
 
-         $command = strtolower($token->text);
 
-         // Check the registry for $command
 
-         if ($this->registry_->isCommand($command))
 
-         {
 
-             $xml = $this->registry_->command($command);
 
-             $this->arguments_ = $this->makeArguments_($xml);
 
-             $this->followupToken_ = SieveToken::Semicolon;
 
-         }
 
-         else if ($this->registry_->isTest($command))
 
-         {
 
-             $xml = $this->registry_->test($command);
 
-             $this->arguments_ = $this->makeArguments_($xml);
 
-             $this->followupToken_ = SieveToken::BlockStart;
 
-         }
 
-         else
 
-         {
 
-             throw new SieveException($token, 'unknown command '. $command);
 
-         }
 
-         // Check if command may appear at this position within the script
 
-         if ($this->registry_->isTest($command))
 
-         {
 
-             if (is_null($prevToken))
 
-                 throw new SieveException($token, $command .' may not appear as first command');
 
-             if (!preg_match('/^(if|elsif|anyof|allof|not)$/i', $prevToken->text))
 
-                 throw new SieveException($token, $command .' may not appear after '. $prevToken->text);
 
-         }
 
-         else if (isset($prevToken))
 
-         {
 
-             switch ($command)
 
-             {
 
-             case 'require':
 
-                 $valid_after = 'require';
 
-                 break;
 
-             case 'elsif':
 
-             case 'else':
 
-                 $valid_after = '(if|elsif)';
 
-                 break;
 
-             default:
 
-                 $valid_after = $this->commandsRegex_();
 
-             }
 
-             if (!preg_match('/^'. $valid_after .'$/i', $prevToken->text))
 
-                 throw new SieveException($token, $command .' may not appear after '. $prevToken->text);
 
-         }
 
-         // Check for extension arguments to add to the command
 
-         foreach ($this->registry_->arguments($command) as $arg)
 
-         {
 
-             switch ((string) $arg['type'])
 
-             {
 
-             case 'tag':
 
-                 array_unshift($this->arguments_, array(
 
-                     'type'       => SieveToken::Tag,
 
-                     'occurrence' => $this->occurrence_($arg),
 
-                     'regex'      => $this->regex_($arg),
 
-                     'call'       => 'tagHook_',
 
-                     'name'       => $this->name_($arg),
 
-                     'subArgs'    => $this->makeArguments_($arg->children())
 
-                 ));
 
-                 break;
 
-             }
 
-         }
 
-     }
 
-     public function __destruct()
 
-     {
 
-         $this->registry_->put();
 
-     }
 
-     // TODO: the *Regex functions could possibly also be static properties
 
-     protected function requireStringsRegex_()
 
-     {
 
-         return '('. implode('|', $this->registry_->requireStrings()) .')';
 
-     }
 
-     protected function matchTypeRegex_()
 
-     {
 
-         return '('. implode('|', $this->registry_->matchTypes()) .')';
 
-     }
 
-     protected function addressPartRegex_()
 
-     {
 
-         return '('. implode('|', $this->registry_->addressParts()) .')';
 
-     }
 
-     protected function commandsRegex_()
 
-     {
 
-         return '('. implode('|', $this->registry_->commands()) .')';
 
-     }
 
-     protected function testsRegex_()
 
-     {
 
-         return '('. implode('|', $this->registry_->tests()) .')';
 
-     }
 
-     protected function comparatorRegex_()
 
-     {
 
-         return '('. implode('|', $this->registry_->comparators()) .')';
 
-     }
 
-     protected function occurrence_($arg)
 
-     {
 
-         if (isset($arg['occurrence']))
 
-         {
 
-             switch ((string) $arg['occurrence'])
 
-             {
 
-             case 'optional':
 
-                 return '?';
 
-             case 'any':
 
-                 return '*';
 
-             case 'some':
 
-                 return '+';
 
-             }
 
-         }
 
-         return '1';
 
-     }
 
-     protected function name_($arg)
 
-     {
 
-         if (isset($arg['name']))
 
-         {
 
-             return (string) $arg['name'];
 
-         }
 
-         return (string) $arg['type'];
 
-     }
 
-     protected function regex_($arg)
 
-     {
 
-         if (isset($arg['regex']))
 
-         {
 
-             return (string) $arg['regex'];
 
-         }
 
-         return '.*';
 
-     }
 
-     protected function case_($arg)
 
-     {
 
-         if (isset($arg['case']))
 
-         {
 
-             return (string) $arg['case'];
 
-         }
 
-         return 'adhere';
 
-     }
 
-     protected function follows_($arg)
 
-     {
 
-         if (isset($arg['follows']))
 
-         {
 
-             return (string) $arg['follows'];
 
-         }
 
-         return '.*';
 
-     }
 
-     protected function makeValue_($arg)
 
-     {
 
-         if (isset($arg->value))
 
-         {
 
-             $res = $this->makeArguments_($arg->value);
 
-             return array_shift($res);
 
-         }
 
-         return null;
 
-     }
 
-     /**
 
-      * Convert an extension (test) commands parameters from XML to
 
-      * a PHP array the {@see Semantics} class understands.
 
-      * @param array(SimpleXMLElement) $parameters
 
-      * @return array
 
-      */
 
-     protected function makeArguments_($parameters)
 
-     {
 
-         $arguments = array();
 
-         foreach ($parameters as $arg)
 
-         {
 
-             // Ignore anything not a <parameter>
 
-             if ($arg->getName() != 'parameter')
 
-                 continue;
 
-             switch ((string) $arg['type'])
 
-             {
 
-             case 'addresspart':
 
-                 array_push($arguments, array(
 
-                     'type'       => SieveToken::Tag,
 
-                     'occurrence' => $this->occurrence_($arg),
 
-                     'regex'      => $this->addressPartRegex_(),
 
-                     'call'       => 'addressPartHook_',
 
-                     'name'       => 'address part',
 
-                     'subArgs'    => $this->makeArguments_($arg)
 
-                 ));
 
-                 break;
 
-             case 'block':
 
-                 array_push($arguments, array(
 
-                     'type'       => SieveToken::BlockStart,
 
-                     'occurrence' => '1',
 
-                     'regex'      => '{',
 
-                     'name'       => 'block',
 
-                     'subArgs'    => $this->makeArguments_($arg)
 
-                 ));
 
-                 break;
 
-             case 'comparator':
 
-                 array_push($arguments, array(
 
-                     'type'       => SieveToken::Tag,
 
-                     'occurrence' => $this->occurrence_($arg),
 
-                     'regex'      => 'comparator',
 
-                     'name'       => 'comparator',
 
-                     'subArgs'    => array( array(
 
-                         'type'       => SieveToken::String,
 
-                         'occurrence' => '1',
 
-                         'call'       => 'comparatorHook_',
 
-                         'case'       => 'adhere',
 
-                         'regex'      => $this->comparatorRegex_(),
 
-                         'name'       => 'comparator string',
 
-                         'follows'    => 'comparator'
 
-                     ))
 
-                 ));
 
-                 break;
 
-             case 'matchtype':
 
-                 array_push($arguments, array(
 
-                     'type'       => SieveToken::Tag,
 
-                     'occurrence' => $this->occurrence_($arg),
 
-                     'regex'      => $this->matchTypeRegex_(),
 
-                     'call'       => 'matchTypeHook_',
 
-                     'name'       => 'match type',
 
-                     'subArgs'    => $this->makeArguments_($arg)
 
-                 ));
 
-                 break;
 
-             case 'number':
 
-                 array_push($arguments, array(
 
-                     'type'       => SieveToken::Number,
 
-                     'occurrence' => $this->occurrence_($arg),
 
-                     'regex'      => $this->regex_($arg),
 
-                     'name'       => $this->name_($arg),
 
-                     'follows'    => $this->follows_($arg)
 
-                 ));
 
-                 break;
 
-             case 'requirestrings':
 
-                 array_push($arguments, array(
 
-                     'type'       => SieveToken::StringList,
 
-                     'occurrence' => $this->occurrence_($arg),
 
-                     'call'       => 'setRequire_',
 
-                     'case'       => 'adhere',
 
-                     'regex'      => $this->requireStringsRegex_(),
 
-                     'name'       => $this->name_($arg)
 
-                 ));
 
-                 break;
 
-             case 'string':
 
-                 array_push($arguments, array(
 
-                     'type'       => SieveToken::String,
 
-                     'occurrence' => $this->occurrence_($arg),
 
-                     'regex'      => $this->regex_($arg),
 
-                     'case'       => $this->case_($arg),
 
-                     'name'       => $this->name_($arg),
 
-                     'follows'    => $this->follows_($arg)
 
-                 ));
 
-                 break;
 
-             case 'stringlist':
 
-                 array_push($arguments, array(
 
-                     'type'       => SieveToken::StringList,
 
-                     'occurrence' => $this->occurrence_($arg),
 
-                     'regex'      => $this->regex_($arg),
 
-                     'case'       => $this->case_($arg),
 
-                     'name'       => $this->name_($arg),
 
-                     'follows'    => $this->follows_($arg)
 
-                 ));
 
-                 break;
 
-             case 'tag':
 
-                 array_push($arguments, array(
 
-                     'type'       => SieveToken::Tag,
 
-                     'occurrence' => $this->occurrence_($arg),
 
-                     'regex'      => $this->regex_($arg),
 
-                     'call'       => 'tagHook_',
 
-                     'name'       => $this->name_($arg),
 
-                     'subArgs'    => $this->makeArguments_($arg->children()),
 
-                     'follows'    => $this->follows_($arg)
 
-                 ));
 
-                 break;
 
-             case 'test':
 
-                 array_push($arguments, array(
 
-                     'type'       => SieveToken::Identifier,
 
-                     'occurrence' => $this->occurrence_($arg),
 
-                     'regex'      => $this->testsRegex_(),
 
-                     'name'       => $this->name_($arg),
 
-                     'subArgs'    => $this->makeArguments_($arg->children())
 
-                 ));
 
-                 break;
 
-             case 'testlist':
 
-                 array_push($arguments, array(
 
-                     'type'       => SieveToken::LeftParenthesis,
 
-                     'occurrence' => '1',
 
-                     'regex'      => '\(',
 
-                     'name'       => $this->name_($arg),
 
-                     'subArgs'    => null
 
-                 ));
 
-                 array_push($arguments, array(
 
-                     'type'       => SieveToken::Identifier,
 
-                     'occurrence' => '+',
 
-                     'regex'      => $this->testsRegex_(),
 
-                     'name'       => $this->name_($arg),
 
-                     'subArgs'    => $this->makeArguments_($arg->children())
 
-                 ));
 
-                 break;
 
-             }
 
-         }
 
-         return $arguments;
 
-     }
 
-     /**
 
-      * Add argument(s) expected / allowed to appear next.
 
-      * @param array $value
 
-      */
 
-     protected function addArguments_($identifier, $subArgs)
 
-     {
 
-         for ($i = count($subArgs); $i > 0; $i--)
 
-         {
 
-             $arg = $subArgs[$i-1];
 
-             if (preg_match('/^'. $arg['follows'] .'$/si', $identifier))
 
-                 array_unshift($this->arguments_, $arg);
 
-         }
 
-     }
 
-     /**
 
-      * Add dependency that is expected to be fullfilled when parsing
 
-      * of the current command is {@see done}.
 
-      * @param array $dependency
 
-      */
 
-     protected function addDependency_($type, $name, $dependencies)
 
-     {
 
-         foreach ($dependencies as $d)
 
-         {
 
-             array_push($this->deps_, array(
 
-                 'o_type' => $type,
 
-                 'o_name' => $name,
 
-                 'type'   => $d['type'],
 
-                 'name'   => $d['name'],
 
-                 'regex'  => $d['regex']
 
-             ));
 
-         }
 
-     }
 
-     protected function invoke_($token, $func, $arg = array())
 
-     {
 
-         if (!is_array($arg))
 
-             $arg = array($arg);
 
-         $err = call_user_func_array(array(&$this, $func), $arg);
 
-         if ($err)
 
-             throw new SieveException($token, $err);
 
-     }
 
-     protected function setRequire_($extension)
 
-     {
 
-         array_push(self::$requiredExtensions_, $extension);
 
-         $this->registry_->activate($extension);
 
-     }
 
-     /**
 
-      * Hook function that is called after a address part match was found
 
-      * in a command. The kind of address part is remembered in case it's
 
-      * needed later {@see done}. For address parts from a extension
 
-      * dependency information and valid values are looked up as well.
 
-      * @param string $addresspart
 
-      */
 
-     protected function addressPartHook_($addresspart)
 
-     {
 
-         $this->addressPart_ = $addresspart;
 
-         $xml = $this->registry_->addresspart($this->addressPart_);
 
-         if (isset($xml))
 
-         {
 
-             // Add possible value and dependancy
 
-             $this->addArguments_($this->addressPart_, $this->makeArguments_($xml));
 
-             $this->addDependency_('address part', $this->addressPart_, $xml->requires);
 
-         }
 
-     }
 
-     /**
 
-      * Hook function that is called after a match type was found in a
 
-      * command. The kind of match type is remembered in case it's
 
-      * needed later {@see done}. For a match type from extensions
 
-      * dependency information and valid values are looked up as well.
 
-      * @param string $matchtype
 
-      */
 
-     protected function matchTypeHook_($matchtype)
 
-     {
 
-         $this->matchType_ = $matchtype;
 
-         $xml = $this->registry_->matchtype($this->matchType_);
 
-         if (isset($xml))
 
-         {
 
-             // Add possible value and dependancy
 
-             $this->addArguments_($this->matchType_, $this->makeArguments_($xml));
 
-             $this->addDependency_('match type', $this->matchType_, $xml->requires);
 
-         }
 
-     }
 
-     /**
 
-      * Hook function that is called after a comparator was found in
 
-      * a command. The comparator is remembered in case it's needed for
 
-      * comparsion later {@see done}. For a comparator from extensions
 
-      * dependency information is looked up as well.
 
-      * @param string $comparator
 
-      */
 
-     protected function comparatorHook_($comparator)
 
-     {
 
-         $this->comparator_ = $comparator;
 
-         $xml = $this->registry_->comparator($this->comparator_);
 
-         if (isset($xml))
 
-         {
 
-             // Add possible dependancy
 
-             $this->addDependency_('comparator', $this->comparator_, $xml->requires);
 
-         }
 
-     }
 
-     /**
 
-      * Hook function that is called after a tag was found in
 
-      * a command. The tag is remembered in case it's needed for
 
-      * comparsion later {@see done}. For a tags from extensions
 
-      * dependency information is looked up as well.
 
-      * @param string $tag
 
-      */
 
-     protected function tagHook_($tag)
 
-     {
 
-         array_push($this->tags_, $tag);
 
-         $xml = $this->registry_->argument($tag);
 
-         // Add possible dependancies
 
-         if (isset($xml))
 
-             $this->addDependency_('tag', $tag, $xml->requires);
 
-     }
 
-     protected function validType_($token)
 
-     {
 
-         foreach ($this->arguments_ as $arg)
 
-         {
 
-             if ($arg['occurrence'] == '0')
 
-             {
 
-                 array_shift($this->arguments_);
 
-                 continue;
 
-             }
 
-             if ($token->is($arg['type']))
 
-                 return;
 
-             // Is the argument required
 
-             if ($arg['occurrence'] != '?' && $arg['occurrence'] != '*')
 
-                 throw new SieveException($token, $arg['type']);
 
-             array_shift($this->arguments_);
 
-         }
 
-         // Check if command expects any (more) arguments
 
-         if (empty($this->arguments_))
 
-             throw new SieveException($token, $this->followupToken_);
 
-         throw new SieveException($token, 'unexpected '. SieveToken::typeString($token->type) .' '. $token->text);
 
-     }
 
-     public function startStringList($token)
 
-     {
 
-         $this->validType_($token);
 
-         $this->arguments_[0]['type'] = SieveToken::String;
 
-         $this->arguments_[0]['occurrence'] = '+';
 
-     }
 
-     public function continueStringList()
 
-     {
 
-         $this->arguments_[0]['occurrence'] = '+';
 
-     }
 
-     public function endStringList()
 
-     {
 
-         array_shift($this->arguments_);
 
-     }
 
-     public function validateToken($token)
 
-     {
 
-         // Make sure the argument has a valid type
 
-         $this->validType_($token);
 
-         foreach ($this->arguments_ as &$arg)
 
-         {
 
-             // Build regular expression according to argument type
 
-             switch ($arg['type'])
 
-             {
 
-             case SieveToken::String:
 
-             case SieveToken::StringList:
 
-                 $regex = '/^(?:text:[^\n]*\n(?P<one>'. $arg['regex'] .')\.\r?\n?|"(?P<two>'. $arg['regex'] .')")$/'
 
-                        . ($arg['case'] == 'ignore' ? 'si' : 's');
 
-                 break;
 
-             case SieveToken::Tag:
 
-                 $regex = '/^:(?P<one>'. $arg['regex'] .')$/si';
 
-                 break;
 
-             default:
 
-                 $regex = '/^(?P<one>'. $arg['regex'] .')$/si';
 
-             }
 
-             if (preg_match($regex, $token->text, $match))
 
-             {
 
-                 $text = ($match['one'] ? $match['one'] : $match['two']);
 
-                 // Add argument(s) that may now appear after this one
 
-                 if (isset($arg['subArgs']))
 
-                     $this->addArguments_($text, $arg['subArgs']);
 
-                 // Call extra processing function if defined
 
-                 if (isset($arg['call']))
 
-                     $this->invoke_($token, $arg['call'], $text);
 
-                 // Check if a possible value of this argument may occur
 
-                 if ($arg['occurrence'] == '?' || $arg['occurrence'] == '1')
 
-                 {
 
-                     $arg['occurrence'] = '0';
 
-                 }
 
-                 else if ($arg['occurrence'] == '+')
 
-                 {
 
-                     $arg['occurrence'] = '*';
 
-                 }
 
-                 return;
 
-             }
 
-             if ($token->is($arg['type']) && $arg['occurrence'] == 1)
 
-             {
 
-                 throw new SieveException($token,
 
-                     SieveToken::typeString($token->type) ." $token->text where ". $arg['name'] .' expected');
 
-             }
 
-         }
 
-         throw new SieveException($token, 'unexpected '. SieveToken::typeString($token->type) .' '. $token->text);
 
-     }
 
-     public function done($token)
 
-     {
 
-         // Check if there are required arguments left
 
-         foreach ($this->arguments_ as $arg)
 
-         {
 
-             if ($arg['occurrence'] == '+' || $arg['occurrence'] == '1')
 
-                 throw new SieveException($token, $arg['type']);
 
-         }
 
-         // Check if the command depends on use of a certain tag
 
-         foreach ($this->deps_ as $d)
 
-         {
 
-             switch ($d['type'])
 
-             {
 
-             case 'addresspart':
 
-                 $values = array($this->addressPart_);
 
-                 break;
 
-             case 'matchtype':
 
-                 $values = array($this->matchType_);
 
-                 break;
 
-             case 'comparator':
 
-                 $values = array($this->comparator_);
 
-                 break;
 
-             case 'tag':
 
-                 $values = $this->tags_;
 
-                 break;
 
-             }
 
-             foreach ($values as $value)
 
-             {
 
-                 if (preg_match('/^'. $d['regex'] .'$/mi', $value))
 
-                     break 2;
 
-             }
 
-             throw new SieveException($token,
 
-                 $d['o_type'] .' '. $d['o_name'] .' requires use of '. $d['type'] .' '. $d['name']);
 
-         }
 
-     }
 
- }
 
 
  |