[Svn] r4447 - in trunk/plugins/managesieve: . lib tests

trac at roundcube.net trac at roundcube.net
Sun Jan 23 09:26:19 CET 2011


Author: alec
Date: 2011-01-23 02:26:17 -0600 (Sun, 23 Jan 2011)
New Revision: 4447

Added:
   trunk/plugins/managesieve/tests/
   trunk/plugins/managesieve/tests/Makefile
   trunk/plugins/managesieve/tests/parser.phpt
   trunk/plugins/managesieve/tests/tokenize.phpt
Modified:
   trunk/plugins/managesieve/Changelog
   trunk/plugins/managesieve/lib/rcube_sieve.php
Log:
- Rewritten sieve script parser, added tests


Modified: trunk/plugins/managesieve/Changelog
===================================================================
--- trunk/plugins/managesieve/Changelog	2011-01-22 16:30:52 UTC (rev 4446)
+++ trunk/plugins/managesieve/Changelog	2011-01-23 08:26:17 UTC (rev 4447)
@@ -2,6 +2,8 @@
 - Fixed parsing of scripts with \r\n line separator
 - Apply forgotten changes for form errors handling
 - Fix multi-line strings parsing (#1487685)
+- Added tests for script parser
+- Rewritten script parser
 
 * version 2.10 [2010-10-10]
 -----------------------------------------------------------

Modified: trunk/plugins/managesieve/lib/rcube_sieve.php
===================================================================
--- trunk/plugins/managesieve/lib/rcube_sieve.php	2011-01-22 16:30:52 UTC (rev 4446)
+++ trunk/plugins/managesieve/lib/rcube_sieve.php	2011-01-23 08:26:17 UTC (rev 4447)
@@ -513,14 +513,11 @@
                     $tests[$i] .= 'size :' . ($test['type']=='under' ? 'under ' : 'over ') . $test['arg'];
                     break;
                 case 'true':
-                    $tests[$i] .= ($test['not'] ? 'not true' : 'true');
+                    $tests[$i] .= ($test['not'] ? 'false' : 'true');
                     break;
                 case 'exists':
                     $tests[$i] .= ($test['not'] ? 'not ' : '');
-                    if (is_array($test['arg']))
-                        $tests[$i] .= 'exists ["' . implode('", "', $this->_escape_string($test['arg'])) . '"]';
-                    else
-                        $tests[$i] .= 'exists "' . $this->_escape_string($test['arg']) . '"';
+                    $tests[$i] .= 'exists ' . self::escape_string($test['arg']);
                     break;
                 case 'header':
                     $tests[$i] .= ($test['not'] ? 'not ' : '');
@@ -534,16 +531,8 @@
                     else
                         $tests[$i] .= 'header :' . $test['type'];
                     
-                    if (is_array($test['arg1']))
-                        $tests[$i] .= ' ["' . implode('", "', $this->_escape_string($test['arg1'])) . '"]';
-                    else
-                        $tests[$i] .= ' "' . $this->_escape_string($test['arg1']) . '"';
-
-                    if (is_array($test['arg2']))
-                        $tests[$i] .= ' ["' . implode('", "', $this->_escape_string($test['arg2'])) . '"]';
-                    else
-                        $tests[$i] .= ' "' . $this->_escape_string($test['arg2']) . '"';
-
+                    $tests[$i] .= ' ' . self::escape_string($test['arg1']);
+                    $tests[$i] .= ' ' . self::escape_string($test['arg2']);
                     break;
                 }
                 $i++;
@@ -571,7 +560,7 @@
                         $script .= ':copy ';
                         array_push($exts, 'copy');
                     }
-                    $script .= "\"" . $this->_escape_string($action['target']) . "\";\n";
+                    $script .= self::escape_string($action['target']) . ";\n";
                     break;
                 case 'redirect':
                     $script .= "\tredirect ";
@@ -579,17 +568,13 @@
                         $script .= ':copy ';
                         array_push($exts, 'copy');
                     }
-                    $script .= "\"" . $this->_escape_string($action['target']) . "\";\n";
+                    $script .= self::escape_string($action['target']) . ";\n";
                     break;
                 case 'reject':
                 case 'ereject':
                     array_push($exts, $action['type']);
-                    if (strpos($action['target'], "\n")!==false)
-                        $script .= "\t".$action['type']." text:\n"
-                            . $this->_escape_multiline_string($action['target']) . "\n.\n;\n";
-                    else
-                        $script .= "\t".$action['type']." \""
-                            . $this->_escape_string($action['target']) . "\";\n";
+                    $script .= "\t".$action['type']." "
+                        . self::escape_string($action['target']) . ";\n";
                     break;
                 case 'keep':
                 case 'discard':
@@ -599,22 +584,19 @@
                 case 'vacation':
                     array_push($exts, 'vacation');
                     $script .= "\tvacation";
-                    if ($action['days'])
+                    if (!empty($action['days']))
                         $script .= " :days " . $action['days'];
-                    if ($action['addresses'])
-                        $script .= " :addresses " . $this->_print_list($action['addresses']);
-                    if ($action['subject'])
-                        $script .= " :subject \"" . $this->_escape_string($action['subject']) . "\"";
-                    if ($action['handle'])
-                        $script .= " :handle \"" . $this->_escape_string($action['handle']) . "\"";
-                    if ($action['from'])
-                        $script .= " :from \"" . $this->_escape_string($action['from']) . "\"";
-                    if ($action['mime'])
+                    if (!empty($action['addresses']))
+                        $script .= " :addresses " . self::escape_string($action['addresses']);
+                    if (!empty($action['subject']))
+                        $script .= " :subject " . self::escape_string($action['subject']);
+                    if (!empty($action['handle']))
+                        $script .= " :handle " . self::escape_string($action['handle']);
+                    if (!empty($action['from']))
+                        $script .= " :from " . self::escape_string($action['from']);
+                    if (!empty($action['mime']))
                         $script .= " :mime";
-                    if (strpos($action['reason'], "\n")!==false)
-                        $script .= " text:\n" . $this->_escape_multiline_string($action['reason']) . "\n.\n;\n";
-                    else
-                        $script .= " \"" . $this->_escape_string($action['reason']) . "\";\n";
+                    $script .= " " . self::escape_string($action['reason']) . ";\n";
                     break;
                 }
             }
@@ -658,9 +640,6 @@
         $i = 0;
         $content = array();
 
-        // remove C comments
-        $script = preg_replace('|/\*.*?\*/|sm', '', $script);
-
         // tokenize rules
         if ($tokens = preg_split('/(# rule:\[.*\])\r?\n/', $script, -1, PREG_SPLIT_DELIM_CAPTURE)) {
             foreach($tokens as $token) {
@@ -688,345 +667,396 @@
      */
     private function _tokenize_rule($content)
     {
-        $result = NULL;
+        $cond = strtolower(self::tokenize($content, 1));
 
-        if (preg_match('/^(if|elsif|else)\s+((true|false|not\s+true|allof|anyof|exists|header|not|size)(.*))\s+\{(.*)\}$/sm',
-            trim($content), $matches)) {
+        if ($cond != 'if' && $cond != 'elsif' && $cond != 'else') {
+            return NULL;
+        }
 
-            $tests = trim($matches[2]);
+        $disabled = false;
+        $join     = false;
 
-            // disabled rule (false + comment): if false #.....
-            if ($matches[3] == 'false') {
-                $tests = preg_replace('/^false\s+#\s+/', '', $tests);
-                $disabled = true;
-            }
-            else
-                $disabled = false;
-
-            list($tests, $join) = $this->_parse_tests($tests);
-            $actions = $this->_parse_actions(trim($matches[5]));
-
-            if ($tests && $actions)
-                $result = array(
-                    'type'     => $matches[1],
-                    'tests'    => $tests,
-                    'actions'  => $actions,
-                    'join'     => $join,
-                    'disabled' => $disabled,
-            );
+        // disabled rule (false + comment): if false # .....
+        if (preg_match('/^\s*false\s+#/i', $content)) {
+            $content = preg_replace('/^\s*false\s+#\s*/i', '', $content);
+            $disabled = true;
         }
 
-        return $result;
-    }
+        while (strlen($content)) {
+            $tokens = self::tokenize($content, true);
+            $separator = array_pop($tokens);
 
-    /**
-     * Parse body of actions section
-     *
-     * @param string Text body
-     * @return array Array of parsed action type/target pairs
-     */
-    private function _parse_actions($content)
-    {
-        $result = NULL;
+            if (!empty($tokens)) {
+                $token = array_shift($tokens);
+            }
+            else {
+                $token = $separator;
+            }
 
-        // supported actions
-        $patterns[] = '^\s*discard;';
-        $patterns[] = '^\s*keep;';
-        $patterns[] = '^\s*stop;';
-        $patterns[] = '^\s*redirect\s+(.*?[^\\\]);';
-        if (in_array('fileinto', $this->supported))
-            $patterns[] = '^\s*fileinto\s+(.*?[^\\\]);';
-        if (in_array('reject', $this->supported)) {
-            $patterns[] = '^\s*reject\s+text:(.*)\n\.\n;';
-            $patterns[] = '^\s*reject\s+(.*?[^\\\]);';
-            $patterns[] = '^\s*ereject\s+text:(.*)\n\.\n;';
-            $patterns[] = '^\s*ereject\s+(.*?[^\\\]);';
-        }
-        if (in_array('vacation', $this->supported))
-            $patterns[] = '^\s*vacation\s+(.*?[^\\\]);';
+            $token = strtolower($token);
 
-        $pattern = '/(' . implode('\s*$)|(', $patterns) . '$\s*)/ms';
+            if ($token == 'not') {
+                $not = true;
+                $token = strtolower(array_shift($tokens));
+            }
+            else {
+                $not = false;
+            }
 
-        // parse actions body
-        if (preg_match_all($pattern, $content, $mm, PREG_SET_ORDER)) {
-            foreach ($mm as $m) {
-                $content = trim($m[0]);
+            switch ($token) {
+            case 'allof':
+                $join = true;
+                break;
+            case 'anyof':
+                break;
 
-                if(preg_match('/^(discard|keep|stop)/', $content, $matches)) {
-                    $result[] = array('type' => $matches[1]);
-                }
-                else if(preg_match('/^fileinto/', $content)) {
-                    $target = $m[sizeof($m)-1];
-                    $copy = false;
-                    if (preg_match('/^:copy\s+/', $target)) {
-                        $target = preg_replace('/^:copy\s+/', '', $target);
-                        $copy = true;
+            case 'size':
+                $size = array('test' => 'size', 'not'  => $not);
+                for ($i=0, $len=count($tokens); $i<$len; $i++) {
+                    if (!is_array($tokens[$i])
+                        && preg_match('/^:(under|over)$/i', $tokens[$i])
+                    ) {
+                        $size['type'] = strtolower(substr($tokens[$i], 1));
                     }
-                    $result[] = array('type' => 'fileinto', 'copy' => $copy,
-                        'target' => $this->_parse_string($target));
-                }
-                else if(preg_match('/^redirect/', $content)) {
-                    $target = $m[sizeof($m)-1];
-                    $copy = false;
-                    if (preg_match('/^:copy\s+/', $target)) {
-                        $target = preg_replace('/^:copy\s+/', '', $target);
-                        $copy = true;
+                    else {
+                        $size['arg'] = $tokens[$i];
                     }
-                    $result[] = array('type' => 'redirect', 'copy' => $copy,
-                        'target' => $this->_parse_string($target));
                 }
-                else if(preg_match('/^(reject|ereject)\s+(.*);$/sm', $content, $matches)) {
-                    $result[] = array('type' => $matches[1], 'target' => $this->_parse_string($matches[2]));
-                }
-                else if(preg_match('/^vacation\s+(.*);$/sm', $content, $matches)) {
-                    $vacation = array('type' => 'vacation');
 
-                    if (preg_match('/:days\s+([0-9]+)/', $content, $vm)) {
-                        $vacation['days'] = $vm[1];
-                        $content = preg_replace('/:days\s+([0-9]+)/', '', $content);
+                $tests[] = $size;
+                break;
+
+            case 'header':
+                $header = array('test' => 'header', 'not' => $not, 'arg1' => '', 'arg2' => '');
+                for ($i=0, $len=count($tokens); $i<$len; $i++) {
+                    if (!is_array($tokens[$i]) && preg_match('/^:comparator$/i', $tokens[$i])) {
+                        $i++;
                     }
-                    if (preg_match('/:subject\s+"(.*?[^\\\])"/', $content, $vm)) {
-                        $vacation['subject'] = $vm[1];
-                        $content = preg_replace('/:subject\s+"(.*?[^\\\])"/', '', $content);
+                    else if (!is_array($tokens[$i]) && preg_match('/^:(count|value)$/i', $tokens[$i])) {
+                        $header['type'] = strtolower(substr($tokens[$i], 1)) . '-' . $tokens[++$i];
                     }
-                    if (preg_match('/:addresses\s+\[(.*?[^\\\])\]/', $content, $vm)) {
-                        $vacation['addresses'] = $this->_parse_list($vm[1]);
-                        $content = preg_replace('/:addresses\s+\[(.*?[^\\\])\]/', '', $content);
+                    else if (!is_array($tokens[$i]) && preg_match('/^:(is|contains|matches)$/i', $tokens[$i])) {
+                        $header['type'] = strtolower(substr($tokens[$i], 1));
                     }
-                    if (preg_match('/:handle\s+"(.*?[^\\\])"/', $content, $vm)) {
-                        $vacation['handle'] = $vm[1];
-                        $content = preg_replace('/:handle\s+"(.*?[^\\\])"/', '', $content);
+                    else {
+                        $header['arg1'] = $header['arg2'];
+