git subrepo clone https://github.com/mailcow/mailcow-dockerized.git mailcow/src/mailcow-dockerized
subrepo: subdir: "mailcow/src/mailcow-dockerized"
merged: "a832becb"
upstream: origin: "https://github.com/mailcow/mailcow-dockerized.git"
branch: "master"
commit: "a832becb"
git-subrepo: version: "0.4.3"
origin: "???"
commit: "???"
Change-Id: If5be2d621a211e164c9b6577adaa7884449f16b5
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/sieve/SieveScanner.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/sieve/SieveScanner.php
new file mode 100644
index 0000000..a0fa57a
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/sieve/SieveScanner.php
@@ -0,0 +1,145 @@
+<?php namespace Sieve;
+
+include_once('SieveToken.php');
+
+class SieveScanner
+{
+ public function __construct(&$script)
+ {
+ if ($script === null)
+ return;
+
+ $this->tokenize($script);
+ }
+
+ public function setPassthroughFunc($callback)
+ {
+ if ($callback == null || is_callable($callback))
+ $this->ptFn_ = $callback;
+ }
+
+ public function tokenize(&$script)
+ {
+ $pos = 0;
+ $line = 1;
+
+ $scriptLength = mb_strlen($script);
+
+ $unprocessedScript = $script;
+
+
+ //create one regex to find the right match
+ //avoids looping over all possible tokens: increases performance
+ $nameToType = [];
+ $regex = [];
+ // chr(65) == 'A'
+ $i = 65;
+
+ foreach ($this->tokenMatch_ as $type => $subregex) {
+ $nameToType[chr($i)] = $type;
+ $regex[] = "(?P<". chr($i) . ">^$subregex)";
+ $i++;
+ }
+
+ $regex = '/' . join('|', $regex) . '/';
+
+ while ($pos < $scriptLength)
+ {
+ if (preg_match($regex, $unprocessedScript, $match)) {
+
+ // only keep the group that match and we only want matches with group names
+ // we can use the group name to find the token type using nameToType
+ $filterMatch = array_filter(array_filter($match), 'is_string', ARRAY_FILTER_USE_KEY);
+
+ // the first element in filterMatch will contain the matched group and the key will be the name
+ $type = $nameToType[key($filterMatch)];
+ $currentMatch = current($filterMatch);
+
+ //create the token
+ $token = new SieveToken($type, $currentMatch, $line);
+ $this->tokens_[] = $token;
+
+ if ($type == SieveToken::Unknown)
+ return;
+
+ // just remove the part that we parsed: don't extract the new substring using script length
+ // as mb_strlen is \theta(pos) (it's linear in the position)
+ $matchLength = mb_strlen($currentMatch);
+ $unprocessedScript = mb_substr($unprocessedScript, $matchLength);
+
+ $pos += $matchLength;
+ $line += mb_substr_count($currentMatch, "\n");
+ } else {
+ $this->tokens_[] = new SieveToken(SieveToken::Unknown, '', $line);
+ return;
+ }
+
+ }
+
+ $this->tokens_[] = new SieveToken(SieveToken::ScriptEnd, '', $line);
+ }
+
+ public function nextTokenIs($type)
+ {
+ return $this->peekNextToken()->is($type);
+ }
+
+ public function peekNextToken()
+ {
+ $offset = 0;
+ do {
+ $next = $this->tokens_[$this->tokenPos_ + $offset++];
+ } while ($next->is(SieveToken::Comment|SieveToken::Whitespace));
+
+ return $next;
+ }
+
+ public function nextToken()
+ {
+ $token = $this->tokens_[$this->tokenPos_++];
+
+ while ($token->is(SieveToken::Comment|SieveToken::Whitespace))
+ {
+ if ($this->ptFn_ != null)
+ call_user_func($this->ptFn_, $token);
+
+ $token = $this->tokens_[$this->tokenPos_++];
+ }
+
+ return $token;
+ }
+
+ protected $ptFn_ = null;
+ protected $tokenPos_ = 0;
+ protected $tokens_ = array();
+ protected $tokenMatch_ = array (
+ SieveToken::LeftBracket => '\[',
+ SieveToken::RightBracket => '\]',
+ SieveToken::BlockStart => '\{',
+ SieveToken::BlockEnd => '\}',
+ SieveToken::LeftParenthesis => '\(',
+ SieveToken::RightParenthesis => '\)',
+ SieveToken::Comma => ',',
+ SieveToken::Semicolon => ';',
+ SieveToken::Whitespace => '[ \r\n\t]+',
+ SieveToken::Tag => ':[[:alpha:]_][[:alnum:]_]*(?=\b)',
+ /*
+ " # match a quotation mark
+ ( # start matching parts that include an escaped quotation mark
+ ([^"]*[^"\\\\]) # match a string without quotation marks and not ending with a backlash
+ ? # this also includes the empty string
+ (\\\\\\\\)* # match any groups of even number of backslashes
+ # (thus the character after these groups are not escaped)
+ \\\\" # match an escaped quotation mark
+ )* # accept any number of strings that end with an escaped quotation mark
+ [^"]* # accept any trailing part that does not contain any quotation marks
+ " # end of the quoted string
+ */
+ SieveToken::QuotedString => '"(([^"]*[^"\\\\])?(\\\\\\\\)*\\\\")*[^"]*"',
+ SieveToken::Number => '[[:digit:]]+(?:[KMG])?(?=\b)',
+ SieveToken::Comment => '(?:\/\*(?:[^\*]|\*(?=[^\/]))*\*\/|#[^\r\n]*\r?(\n|$))',
+ SieveToken::MultilineString => 'text:[ \t]*(?:#[^\r\n]*)?\r?\n(\.[^\r\n]+\r?\n|[^\.][^\r\n]*\r?\n)*\.\r?(\n|$)',
+ SieveToken::Identifier => '[[:alpha:]_][[:alnum:]_]*(?=\b)',
+ SieveToken::Unknown => '[^ \r\n\t]+'
+ );
+}