source: github/plugins/managesieve/lib/rcube_sieve.php @ 98c489e

HEADcourier-fixdev-browser-capabilitiespdorelease-0.6release-0.7release-0.8
Last change on this file since 98c489e was 98c489e, checked in by alecpl <alec@…>, 3 years ago
  • Managesieve: Added import from Horde-INGO
  • Managesieve: Support for more than one match using if+stop instead of if+elsif structures (#1486078)
  • Managesieve: Support for selectively disabling rules within a single sieve script (#1485882)
  • Managesieve: Added vertical splitter
  • Property mode set to 100644
File size: 25.2 KB
Line 
1<?php
2
3/*
4  Classes for managesieve operations (using PEAR::Net_Sieve)
5
6  Author: Aleksander Machniak <alec@alec.pl>
7
8  $Id$
9
10*/
11
12//  Sieve Language Basics: http://www.ietf.org/rfc/rfc5228.txt
13
14define('SIEVE_ERROR_CONNECTION', 1);
15define('SIEVE_ERROR_LOGIN', 2);
16define('SIEVE_ERROR_NOT_EXISTS', 3);    // script not exists
17define('SIEVE_ERROR_INSTALL', 4);       // script installation
18define('SIEVE_ERROR_ACTIVATE', 5);      // script activation
19define('SIEVE_ERROR_DELETE', 6);        // script deletion
20define('SIEVE_ERROR_INTERNAL', 7);      // internal error
21define('SIEVE_ERROR_OTHER', 255);       // other/unknown error
22
23
24class rcube_sieve
25{
26    private $sieve;                     // Net_Sieve object
27    private $error = false;             // error flag
28    private $list = array();            // scripts list
29
30    public $script;                     // rcube_sieve_script object
31    public $current;                    // name of currently loaded script
32    private $disabled;                  // array of disabled extensions
33
34    /**
35    * Object constructor
36    *
37    * @param  string  Username (to managesieve login)
38    * @param  string  Password (to managesieve login)
39    * @param  string  Managesieve server hostname/address
40    * @param  string  Managesieve server port number
41    * @param  string  Enable/disable TLS use
42    * @param  array   Disabled extensions
43    */
44    public function __construct($username, $password='', $host='localhost', $port=2000,
45                            $usetls=true, $disabled=array(), $debug=false)
46    {
47        $this->sieve = new Net_Sieve();
48     
49        if ($debug)
50            $this->sieve->setDebug(true, array($this, 'debug_handler'));
51     
52        if (PEAR::isError($this->sieve->connect($host, $port, NULL, $usetls)))
53            return $this->_set_error(SIEVE_ERROR_CONNECTION);
54
55        if (PEAR::isError($this->sieve->login($username, $password)))
56            return $this->_set_error(SIEVE_ERROR_LOGIN);
57
58        $this->disabled = $disabled;
59    }
60
61    public function __destruct() {
62        $this->sieve->disconnect();
63    }
64
65    /**
66    * Getter for error code
67    */
68    public function error()
69    {
70        return $this->error ? $this->error : false;
71    }
72                           
73    /**
74    * Saves current script into server
75    */
76    public function save($name = null)
77    {
78        if (!$this->sieve)
79            return $this->_set_error(SIEVE_ERROR_INTERNAL);
80       
81        if (!$this->script)
82            return $this->_set_error(SIEVE_ERROR_INTERNAL);
83       
84        if (!$name)
85            $name = $this->current;
86
87        $script = $this->script->as_text();
88
89        if (!$script)
90            $script = '/* empty script */';
91
92        if (PEAR::isError($this->sieve->installScript($name, $script)))
93            return $this->_set_error(SIEVE_ERROR_INSTALL);
94
95        return true;
96    }
97
98    /**
99    * Saves text script into server
100    */
101    public function save_script($name, $content = null)
102    {
103        if (!$this->sieve)
104            return $this->_set_error(SIEVE_ERROR_INTERNAL);
105       
106        if (!$content)
107            $content = '/* empty script */';
108
109        if (PEAR::isError($this->sieve->installScript($name, $content)))
110            return $this->_set_error(SIEVE_ERROR_INSTALL);
111
112        return true;
113    }
114
115    /**
116    * Activates specified script
117    */
118    public function activate($name = null)
119    {
120        if (!$this->sieve)
121            return $this->_set_error(SIEVE_ERROR_INTERNAL);
122
123        if (!$name)
124            $name = $this->current;
125
126        if (PEAR::isError($this->sieve->setActive($name)))
127            return $this->_set_error(SIEVE_ERROR_ACTIVATE);
128
129        return true;
130    }
131
132    /**
133    * Removes specified script
134    */
135    public function remove($name = null)
136    {
137        if (!$this->sieve)
138            return $this->_set_error(SIEVE_ERROR_INTERNAL);
139
140        if (!$name)
141            $name = $this->current;
142
143        // script must be deactivated first
144        if ($name == $this->sieve->getActive())
145            if (PEAR::isError($this->sieve->setActive('')))
146                return $this->_set_error(SIEVE_ERROR_DELETE);
147
148        if (PEAR::isError($this->sieve->removeScript($name)))
149            return $this->_set_error(SIEVE_ERROR_DELETE);
150
151        if ($name == $this->current)
152            $this->current = null;
153
154        return true;
155    }
156
157    /**
158    * Gets list of supported by server Sieve extensions
159    */
160    public function get_extensions()
161    {
162        if (!$this->sieve)
163            return $this->_set_error(SIEVE_ERROR_INTERNAL);
164       
165        $ext = $this->sieve->getExtensions();
166        // we're working on lower-cased names
167        $ext = array_map('strtolower', (array) $ext); 
168
169        if ($this->script) {
170            $supported = $this->script->get_extensions();
171            foreach ($ext as $idx => $ext_name)
172                if (!in_array($ext_name, $supported))
173                    unset($ext[$idx]);
174        }
175
176        return array_values($ext);
177    }
178
179    /**
180    * Gets list of scripts from server
181    */
182    public function get_scripts()
183    {
184        if (!$this->list) {
185
186            if (!$this->sieve)
187                return $this->_set_error(SIEVE_ERROR_INTERNAL);
188   
189            $this->list = $this->sieve->listScripts();
190
191            if (PEAR::isError($this->list))
192                return $this->_set_error(SIEVE_ERROR_OTHER);
193        }
194
195        return $this->list;
196    }
197
198    /**
199    * Returns active script name
200    */
201    public function get_active()
202    {
203        if (!$this->sieve)
204            return $this->_set_error(SIEVE_ERROR_INTERNAL);
205
206        return $this->sieve->getActive();
207    }
208   
209    /**
210    * Loads script by name
211    */
212    public function load($name)
213    {
214        if (!$this->sieve)
215            return $this->_set_error(SIEVE_ERROR_INTERNAL);
216
217        if ($this->current == $name)
218            return true;
219   
220        $script = $this->sieve->getScript($name);
221   
222        if (PEAR::isError($script))
223            return $this->_set_error(SIEVE_ERROR_OTHER);
224
225        // try to parse from Roundcube format
226        $this->script = new rcube_sieve_script($script, $this->disabled);
227
228        // ... else try to import from different formats
229        if (empty($this->script->content)) {
230            $script = $this->_import_rules($script);
231            $this->script = new rcube_sieve_script($script, $this->disabled);
232        }
233
234        // replace all elsif with if+stop, we support only ifs
235        foreach ($this->script->content as $idx => $rule) {
236            if (!isset($this->script->content[$idx+1])
237                || preg_match('/^else|elsif$/', $this->script->content[$idx+1]['type'])) {
238                // 'stop' not found?
239                if (!preg_match('/^(stop|vacation)$/', $rule['actions'][count($rule['actions'])-1]['type'])) {
240                    $this->script->content[$idx]['actions'][] = array(
241                        'type' => 'stop'
242                    );
243                }
244            }
245        }
246
247        $this->current = $name;
248
249        return true;
250    }
251
252    /**
253    * Creates empty script or copy of other script
254    */
255    public function copy($name, $copy)
256    {
257        if (!$this->sieve)
258            return $this->_set_error(SIEVE_ERROR_INTERNAL);
259
260        if ($copy) {
261            $content = $this->sieve->getScript($copy);
262   
263            if (PEAR::isError($content))
264                return $this->_set_error(SIEVE_ERROR_OTHER);
265        }
266       
267        return $this->save_script($name, $content);
268    }
269
270
271    private function _import_rules($script)
272    {
273        $i = 0;
274        $name = array();
275
276        // Squirrelmail (Avelsieve)
277        if ($tokens = preg_split('/(#START_SIEVE_RULE.*END_SIEVE_RULE)\n/', $script, -1, PREG_SPLIT_DELIM_CAPTURE)) {
278            foreach($tokens as $token) {
279                if (preg_match('/^#START_SIEVE_RULE.*/', $token, $matches)) {
280                    $name[$i] = "unnamed rule ".($i+1);
281                    $content .= "# rule:[".$name[$i]."]\n";
282                }
283                elseif (isset($name[$i])) {
284                    $content .= "if $token\n";
285                    $i++;
286                }
287            }
288        }
289        // Horde (INGO)
290        else if ($tokens = preg_split('/(# .+)\r?\n/i', $script, -1, PREG_SPLIT_DELIM_CAPTURE)) {
291            foreach($tokens as $token) {
292                if (preg_match('/^# (.+)/i', $token, $matches)) {
293                    $name[$i] = $matches[1];
294                    $content .= "# rule:[" . $name[$i] . "]\n";
295                }
296                elseif (isset($name[$i])) {
297                    $token = str_replace(":comparator \"i;ascii-casemap\" ", "", $token);
298                    $content .= $token . "\n";
299                    $i++;
300                }
301            }
302        }
303
304        return $content;
305    }
306
307    private function _set_error($error)
308    {
309        $this->error = $error;
310        return false;
311    }
312
313    /**
314    * This is our own debug handler for connection
315    * @access public
316    */
317    public function debug_handler(&$sieve, $message)
318    {
319        write_log('sieve', preg_replace('/\r\n$/', '', $message));
320    }                   
321}
322
323class rcube_sieve_script
324{
325    public $content = array();  // script rules array   
326
327    private $supported = array( // extensions supported by class
328        'fileinto',
329        'reject',
330        'ereject',
331        'vacation',     // RFC5230
332    // TODO: (most wanted first) body, imapflags, notify, regex
333    );
334 
335    /**
336    * Object constructor
337    *
338    * @param  string  Script's text content
339    * @param  array   Disabled extensions
340    */
341    public function __construct($script, $disabled=NULL)
342    {
343        if (!empty($disabled))
344            foreach ($disabled as $ext)
345                if (($idx = array_search($ext, $this->supported)) !== false)
346                    unset($this->supported[$idx]);
347
348        $this->content = $this->_parse_text($script);
349    }
350
351    /**
352    * Adds script contents as text to the script array (at the end)
353    *
354    * @param    string  Text script contents
355    */
356    public function add_text($script)
357    {
358        $content = $this->_parse_text($script);
359        $result = false;
360
361        // check existsing script rules names
362        foreach ($this->content as $idx => $elem) {
363            $names[$elem['name']] = $idx;
364        }
365
366        foreach ($content as $elem) {
367            if (!isset($names[$elem['name']])) {
368                array_push($this->content, $elem);
369                $result = true;
370            }
371        }
372
373        return $result;
374    }
375
376    /**
377    * Adds rule to the script (at the end)
378    *
379    * @param    string  Rule name
380    * @param    array   Rule content (as array)
381    */
382    public function add_rule($content)
383    {
384        // TODO: check this->supported
385        array_push($this->content, $content);
386        return sizeof($this->content)-1;
387    }
388
389    public function delete_rule($index)
390    {
391        if(isset($this->content[$index])) {
392            unset($this->content[$index]);
393            return true;
394        }
395        return false;
396    }
397
398    public function size()
399    {
400        return sizeof($this->content);
401    }
402
403    public function update_rule($index, $content)
404    {
405        // TODO: check this->supported
406        if ($this->content[$index]) {
407            $this->content[$index] = $content;
408            return $index;
409        }
410        return false;
411    }
412
413    /**
414    * Returns script as text
415    */
416    public function as_text()
417    {
418        $script = '';
419        $exts = array();
420        $idx = 0;
421
422        // rules
423        foreach ($this->content as $rule) {
424            $extension = '';
425            $tests = array();
426            $i = 0;
427
428            // header
429            $script .= '# rule:[' . $rule['name'] . "]\n";
430 
431            // constraints expressions
432            foreach ($rule['tests'] as $test) {
433                $tests[$i] = '';
434                switch ($test['test']) {
435                    case 'size':
436                        $tests[$i] .= ($test['not'] ? 'not ' : '');
437                        $tests[$i] .= 'size :' . ($test['type']=='under' ? 'under ' : 'over ') . $test['arg'];
438                    break;
439                    case 'true':
440                        $tests[$i] .= ($test['not'] ? 'not true' : 'true');
441                    break;
442                    case 'exists':
443                        $tests[$i] .= ($test['not'] ? 'not ' : '');
444                        if (is_array($test['arg']))
445                            $tests[$i] .= 'exists ["' . implode('", "', $this->_escape_string($test['arg'])) . '"]';
446                        else
447                            $tests[$i] .= 'exists "' . $this->_escape_string($test['arg']) . '"';
448                    break;   
449                    case 'header':
450                        $tests[$i] .= ($test['not'] ? 'not ' : '');
451                        $tests[$i] .= 'header :' . $test['type'];
452                        if (is_array($test['arg1']))
453                            $tests[$i] .= ' ["' . implode('", "', $this->_escape_string($test['arg1'])) . '"]';
454                        else
455                            $tests[$i] .= ' "' . $this->_escape_string($test['arg1']) . '"';
456                        if (is_array($test['arg2']))
457                            $tests[$i] .= ' ["' . implode('", "', $this->_escape_string($test['arg2'])) . '"]';
458                        else
459                            $tests[$i] .= ' "' . $this->_escape_string($test['arg2']) . '"';
460                    break;
461                }
462                $i++;
463            }
464
465//            $script .= ($idx>0 ? 'els' : '').($rule['join'] ? 'if allof (' : 'if anyof (');
466            // disabled rule: if false #....
467            $script .= 'if' . ($rule['disabled'] ? ' false #' : '');
468            $script .= $rule['join'] ? ' allof (' : ' anyof (';
469            if (sizeof($tests) > 1)
470                $script .= implode(",\n\t", $tests);
471            else if (sizeof($tests))
472                $script .= $tests[0];
473            else
474                $script .= 'true';
475            $script .= ")\n{\n";
476 
477            // action(s)
478            foreach ($rule['actions'] as $action) {
479                switch ($action['type']) {
480                    case 'fileinto':
481                        $extension = 'fileinto';
482                        $script .= "\tfileinto \"" . $this->_escape_string($action['target']) . "\";\n";
483                    break;
484                    case 'redirect':
485                        $script .= "\tredirect \"" . $this->_escape_string($action['target']) . "\";\n";
486                    break;
487                    case 'reject':
488                    case 'ereject':
489                        $extension = $action['type'];
490                        if (strpos($action['target'], "\n")!==false)
491                            $script .= "\t".$action['type']." text:\n" . $action['target'] . "\n.\n;\n";
492                        else
493                            $script .= "\t".$action['type']." \"" . $this->_escape_string($action['target']) . "\";\n";
494                    break;
495                    case 'keep':
496                    case 'discard':
497                    case 'stop':
498                        $script .= "\t" . $action['type'] .";\n";
499                    break;
500                    case 'vacation':
501                        $extension = 'vacation';
502                        $script .= "\tvacation";
503                        if ($action['days'])
504                            $script .= " :days " . $action['days'];
505                        if ($action['addresses'])
506                            $script .= " :addresses " . $this->_print_list($action['addresses']);
507                        if ($action['subject'])
508                            $script .= " :subject \"" . $this->_escape_string($action['subject']) . "\"";
509                        if ($action['handle'])
510                            $script .= " :handle \"" . $this->_escape_string($action['handle']) . "\"";
511                        if ($action['from'])
512                            $script .= " :from \"" . $this->_escape_string($action['from']) . "\"";
513                        if ($action['mime'])
514                            $script .= " :mime";
515                        if (strpos($action['reason'], "\n")!==false)
516                            $script .= " text:\n" . $action['reason'] . "\n.\n;\n";
517                        else
518                            $script .= " \"" . $this->_escape_string($action['reason']) . "\";\n";
519                    break;
520                }
521
522                if ($extension && !isset($exts[$extension]))
523                    $exts[$extension] = $extension;
524            }
525
526            $script .= "}\n";
527            $idx++;
528        }
529     
530        // requires
531        if (sizeof($exts))
532            $script = 'require ["' . implode('","', $exts) . "\"];\n" . $script;
533
534        return $script;
535    }
536
537    /**
538    * Returns script object
539    *
540    */
541    public function as_array()
542    {
543        return $this->content;
544    }
545
546    /**
547    * Returns array of supported extensions
548    *
549    */
550    public function get_extensions()
551    {
552        return array_values($this->supported);
553    }
554
555    /**
556    * Converts text script to rules array
557    *
558    * @param    string  Text script
559    */
560    private function _parse_text($script)
561    {
562        $i = 0;
563        $content = array();
564
565        // remove C comments
566        $script = preg_replace('|/\*.*?\*/|sm', '', $script);
567
568        // tokenize rules
569        if ($tokens = preg_split('/(# rule:\[.*\])\r?\n/', $script, -1, PREG_SPLIT_DELIM_CAPTURE)) {
570            foreach($tokens as $token) {
571                if (preg_match('/^# rule:\[(.*)\]/', $token, $matches)) {
572                    $content[$i]['name'] = $matches[1];
573                }
574                elseif (isset($content[$i]['name']) && sizeof($content[$i]) == 1) {
575                    if ($rule = $this->_tokenize_rule($token)) {
576                        $content[$i] = array_merge($content[$i], $rule);
577                        $i++;
578                    }
579                    else // unknown rule format
580                        unset($content[$i]);
581                }
582            }
583        }
584
585        return $content;
586    }
587
588    /**
589    * Convert text script fragment to rule object
590    *
591    * @param    string  Text rule
592    */
593    private function _tokenize_rule($content)
594    {
595        $result = NULL;
596   
597        if (preg_match('/^(if|elsif|else)\s+((true|false|not\s+true|allof|anyof|exists|header|not|size)(.*))\s+\{(.*)\}$/sm',
598            trim($content), $matches)) {
599
600            $tests = trim($matches[2]);
601
602            // disabled rule (false + comment): if false #.....
603            if ($matches[3] == 'false') {
604                $tests = preg_replace('/^false\s+#\s+/', '', $tests);
605                $disabled = true;
606            }
607            else
608                $disabled = false;
609
610            list($tests, $join) = $this->_parse_tests($tests);
611            $actions = $this->_parse_actions(trim($matches[5]));
612
613            if ($tests && $actions)
614                $result = array(
615                    'type' => $matches[1],
616                    'tests' => $tests,
617                    'actions' => $actions,
618                    'join' => $join,
619                    'disabled' => $disabled,
620                );
621        }
622
623        return $result;
624    }   
625
626    /**
627    * Parse body of actions section
628    *
629    * @param    string  Text body
630    * @return   array   Array of parsed action type/target pairs
631    */
632    private function _parse_actions($content)
633    {
634        $result = NULL;
635
636        // supported actions
637        $patterns[] = '^\s*discard;';
638        $patterns[] = '^\s*keep;';
639        $patterns[] = '^\s*stop;';
640        $patterns[] = '^\s*redirect\s+(.*?[^\\\]);';
641        if (in_array('fileinto', $this->supported))
642            $patterns[] = '^\s*fileinto\s+(.*?[^\\\]);';
643        if (in_array('reject', $this->supported)) {
644            $patterns[] = '^\s*reject\s+text:(.*)\n\.\n;';
645            $patterns[] = '^\s*reject\s+(.*?[^\\\]);';
646            $patterns[] = '^\s*ereject\s+text:(.*)\n\.\n;';
647            $patterns[] = '^\s*ereject\s+(.*?[^\\\]);';
648        }
649        if (in_array('vacation', $this->supported))
650            $patterns[] = '^\s*vacation\s+(.*?[^\\\]);';
651
652        $pattern = '/(' . implode('$)|(', $patterns) . '$)/ms';
653
654        // parse actions body
655        if (preg_match_all($pattern, $content, $mm, PREG_SET_ORDER)) {
656            foreach ($mm as $m) {
657                $content = trim($m[0]);
658         
659                if(preg_match('/^(discard|keep|stop)/', $content, $matches)) {
660                    $result[] = array('type' => $matches[1]);
661                }
662                elseif(preg_match('/^fileinto/', $content)) {
663                    $result[] = array('type' => 'fileinto', 'target' => $this->_parse_string($m[sizeof($m)-1])); 
664                }
665                elseif(preg_match('/^redirect/', $content)) {
666                    $result[] = array('type' => 'redirect', 'target' => $this->_parse_string($m[sizeof($m)-1])); 
667                }
668                elseif(preg_match('/^(reject|ereject)\s+(.*);$/sm', $content, $matches)) {
669                    $result[] = array('type' => $matches[1], 'target' => $this->_parse_string($matches[2])); 
670                }
671                elseif(preg_match('/^vacation\s+(.*);$/sm', $content, $matches)) {
672                    $vacation = array('type' => 'vacation');
673
674                    if (preg_match('/:(days)\s+([0-9]+)/', $content, $vm)) {
675                        $vacation['days'] = $vm[2];
676                        $content = preg_replace('/:(days)\s+([0-9]+)/', '', $content); 
677                    }
678                    if (preg_match('/:(subject)\s+(".*?[^\\\]")/', $content, $vm)) {
679                        $vacation['subject'] = $vm[2];
680                        $content = preg_replace('/:(subject)\s+(".*?[^\\\]")/', '', $content); 
681                    }
682                    if (preg_match('/:(addresses)\s+\[(.*?[^\\\])\]/', $content, $vm)) {
683                        $vacation['addresses'] = $this->_parse_list($vm[2]);
684                        $content = preg_replace('/:(addresses)\s+\[(.*?[^\\\])\]/', '', $content); 
685                    }
686                    if (preg_match('/:(handle)\s+(".*?[^\\\]")/', $content, $vm)) {
687                        $vacation['handle'] = $vm[2];
688                        $content = preg_replace('/:(handle)\s+(".*?[^\\\]")/', '', $content); 
689                    }
690                    if (preg_match('/:(from)\s+(".*?[^\\\]")/', $content, $vm)) {
691                        $vacation['from'] = $vm[2];
692                        $content = preg_replace('/:(from)\s+(".*?[^\\\]")/', '', $content); 
693                    }
694
695                    $content = preg_replace('/^vacation/', '', $content);           
696                    $content = preg_replace('/;$/', '', $content);
697                    $content = trim($content);
698
699                    if (preg_match('/^:(mime)/', $content, $vm)) {
700                        $vacation['mime'] = true;
701                        $content = preg_replace('/^:mime/', '', $content); 
702                    }
703
704                    $vacation['reason'] = $this->_parse_string($content);
705
706                    $result[] = $vacation;
707                }
708            }
709        }
710
711        return $result;
712    }   
713   
714    /**
715    * Parse test/conditions section
716    *
717    * @param    string  Text
718    */
719    private function _parse_tests($content)
720    {
721        $result = NULL;
722
723        // lists
724        if (preg_match('/^(allof|anyof)\s+\((.*)\)$/sm', $content, $matches)) {
725            $content = $matches[2];
726            $join = $matches[1]=='allof' ? true : false;
727        }
728        else
729            $join = false;
730     
731        // supported tests regular expressions
732        // TODO: comparators, envelope
733        $patterns[] = '(not\s+)?(exists)\s+\[(.*?[^\\\])\]';
734        $patterns[] = '(not\s+)?(exists)\s+(".*?[^\\\]")';
735        $patterns[] = '(not\s+)?(true)';
736        $patterns[] = '(not\s+)?(size)\s+:(under|over)\s+([0-9]+[KGM]{0,1})';
737        $patterns[] = '(not\s+)?(header)\s+:(contains|is|matches)\s+\[(.*?[^\\\]")\]\s+\[(.*?[^\\\]")\]';
738        $patterns[] = '(not\s+)?(header)\s+:(contains|is|matches)\s+(".*?[^\\\]")\s+(".*?[^\\\]")';
739        $patterns[] = '(not\s+)?(header)\s+:(contains|is|matches)\s+\[(.*?[^\\\]")\]\s+(".*?[^\\\]")';
740        $patterns[] = '(not\s+)?(header)\s+:(contains|is|matches)\s+(".*?[^\\\]")\s+\[(.*?[^\\\]")\]';
741     
742        // join patterns...
743        $pattern = '/(' . implode(')|(', $patterns) . ')/';
744
745        // ...and parse tests list
746        if (preg_match_all($pattern, $content, $matches, PREG_SET_ORDER)) {
747            foreach ($matches as $match) {
748                $size = sizeof($match);
749             
750                if (preg_match('/^(not\s+)?size/', $match[0])) {
751                    $result[] = array(
752                        'test'  => 'size',
753                        'not'   => $match[$size-4] ? true : false,
754                        'type'  => $match[$size-2], // under/over
755                        'arg'   => $match[$size-1], // value
756                    );
757                }
758                elseif (preg_match('/^(not\s+)?header/', $match[0])) {
759                    $result[] = array(
760                        'test'  => 'header',
761                        'not'   => $match[$size-5] ? true : false,
762                        'type'  => $match[$size-3], // is/contains/matches
763                        'arg1'  => $this->_parse_list($match[$size-2]), // header(s)
764                        'arg2'  => $this->_parse_list($match[$size-1]), // string(s)
765                    ); 
766                }
767                elseif (preg_match('/^(not\s+)?exists/', $match[0])) {
768                    $result[] = array(
769                        'test'  => 'exists',
770                        'not'   => $match[$size-3] ? true : false,
771                        'arg'   => $this->_parse_list($match[$size-1]), // header(s)
772                    );
773                }
774                elseif (preg_match('/^(not\s+)?true/', $match[0])) {
775                    $result[] = array(
776                        'test'  => 'true',
777                        'not'   => $match[$size-2] ? true : false,
778                    );
779                }
780            }
781        }
782
783        return array($result, $join);
784    }   
785
786    /**
787    * Parse string value
788    *
789    * @param    string  Text
790    */
791    private function _parse_string($content)
792    {
793        $text = '';
794        $content = trim($content);
795
796        if (preg_match('/^text:(.*)\.$/sm', $content, $matches))
797            $text = trim($matches[1]);
798        elseif (preg_match('/^"(.*)"$/', $content, $matches))
799            $text = str_replace('\"', '"', $matches[1]);
800
801        return $text;
802    }   
803
804    /**
805    * Escape special chars in string value
806    *
807    * @param    string  Text
808    */
809    private function _escape_string($content)
810    {
811        $replace['/"/'] = '\\"';
812     
813        if (is_array($content)) {
814            for ($x=0, $y=sizeof($content); $x<$y; $x++)
815                $content[$x] = preg_replace(array_keys($replace), array_values($replace), $content[$x]);
816       
817            return $content;
818        }
819        else
820            return preg_replace(array_keys($replace), array_values($replace), $content);
821    }
822
823    /**
824    * Parse string or list of strings to string or array of strings
825    *
826    * @param    string  Text
827    */
828    private function _parse_list($content)
829    {
830        $result = array();
831     
832        for ($x=0, $len=strlen($content); $x<$len; $x++) {
833            switch ($content[$x]) {
834                case '\\':
835                    $str .= $content[++$x];
836                break;
837                case '"':
838                    if (isset($str)) {
839                        $result[] = $str;
840                        unset($str);
841                    }
842                    else
843                        $str = '';
844                break;
845                default:
846                    if(isset($str))
847                        $str .= $content[$x];
848                break;
849            }
850        }
851     
852        if (sizeof($result)>1)
853            return $result;
854        elseif (sizeof($result) == 1)
855            return $result[0];
856        else
857            return NULL;
858    }   
859
860    /**
861    * Convert array of elements to list of strings
862    *
863    * @param    string  Text
864    */
865    private function _print_list($list)
866    {
867        $list = (array) $list;
868        foreach($list as $idx => $val)
869            $list[$idx] = $this->_escape_string($val);
870   
871        return '["' . implode('","', $list) . '"]';
872    }
873}
874
875?>
Note: See TracBrowser for help on using the repository browser.