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']);
- }
- }
- }
|