Code

- Removed obsoleted dir /usr/lib/gosa
[gosa.git] / include / sieve / class_semantics.inc
1 <?php
3 class Semantics
4 {
5         var $registerExtensionFn_;
6         var $isExtensionRegisteredFn_;
8         var $command_;
9         var $comparator_;
10         var $matchType_;
11         var $s_;
12         var $unknown;
13         var $message;
14         var $nonTestCommands_ = '(require|if|elsif|else|reject|fileinto|redirect|stop|keep|discard|mark|unmark|setflag|addflag|removeflag)';
15         var $testsValidAfter_ = '(if|elsif|anyof|allof|not)';
16         var $testCommands_ = '(address|envelope|header|size|allof|anyof|exists|not|true|false)';
17         var $requireStrings_ = '(envelope|fileinto|reject|vacation|relational|subaddress|regex|imapflags|copy)';
19         function Semantics($command)
20         {
21                 $this->command_ = $command;
22                 $this->unknown = false;
23                 switch ($command)
24                 {
26                 /********************
27                  * control commands
28                  */
29                 case 'require':
30                         /* require <capabilities: string-list> */
31                         $this->s_ = array(
32                                 'valid_after' => '(script-start|require)',
33                                 'arguments' => array(
34                                         array('class' => 'string', 'list' => true, 'name' => 'require-string', 'occurrences' => '1', 'call' => 'setRequire_', 'values' => array(
35                                                 array('occurrences' => '+', 'regex' => '"'. $this->requireStrings_ .'"'),
36                                                 array('occurrences' => '+', 'regex' => '"comparator-i;(octet|ascii-casemap|ascii-numeric)"')
37                                         ))
38                                 )
39                         );
40                         break;
42                 case 'if':
43                         /* if <test> <block> */
44                         $this->s_ = array(
45                                 'valid_after' => str_replace('(', '(script-start|', $this->nonTestCommands_),
46                                 'arguments' => array(
47                                         array('class' => 'identifier', 'occurrences' => '1', 'values' => array(
48                                                 array('occurrences' => '1', 'regex' => $this->testCommands_, 'name' => 'test')
49                                         )),
50                                         array('class' => 'block-start', 'occurrences' => '1', 'values' => array(
51                                                 array('occurrences' => '1', 'regex' => '{', 'name' => 'block')
52                                         ))
53                                 )
54                         );
55                         break;
57                 case 'elsif':
58                         /* elsif <test> <block> */
59                         $this->s_ = array(
60                                 'valid_after' => '(if|elsif)',
61                                 'arguments' => array(
62                                         array('class' => 'identifier', 'occurrences' => '1', 'values' => array(
63                                                 array('occurrences' => '1', 'regex' => $this->testCommands_, 'name' => 'test')
64                                         )),
65                                         array('class' => 'block-start', 'occurrences' => '1', 'values' => array(
66                                                 array('occurrences' => '1', 'regex' => '{', 'name' => 'block')
67                                         ))
68                                 )
69                         );
70                         break;
72                 case 'else':
73                         /* else <block> */
74                         $this->s_ = array(
75                                 'valid_after' => '(if|elsif)',
76                                 'arguments' => array(
77                                         array('class' => 'block-start', 'occurrences' => '1', 'values' => array(
78                                                 array('occurrences' => '1', 'regex' => '{', 'name' => 'block')
79                                         ))
80                                 )
81                         );
82                         break;
85                 /*******************
86                  * action commands
87                  */
88                 case 'discard':
89                 case 'keep':
90                 case 'stop':
91                         /* discard / keep / stop */
92                         $this->s_ = array(
93                                 'valid_after' => str_replace('(', '(script-start|', $this->nonTestCommands_)
94                         );
95                         break;
97                 case 'fileinto':
98                         /* fileinto [":copy"] <folder: string> */
99                         $this->s_ = array(
100                                 'requires' => 'fileinto',
101                                 'valid_after' => $this->nonTestCommands_,
102                                 'arguments' => array(
103                                         array('class' => 'tag', 'occurrences' => '?', 'values' => array(
104                                                 array('occurrences' => '?', 'regex' => ':copy', 'requires' => 'copy', 'name' => 'copy')
105                                         )),
106                                         array('class' => 'string', 'occurrences' => '1', 'values' => array(
107                                                 array('occurrences' => '1', 'regex' => '".*"', 'name' => 'folder')
108                                         ))
109                                 )
110                         );
111                         break;
113                 case 'mark':
114                 case 'unmark':
115                         /* mark / unmark */
116                         $this->s_ = array(
117                                 'requires' => 'imapflags',
118                                 'valid_after' => $this->nonTestCommands_
119                         );
120                         break;
122                 case 'redirect':
123                         /* redirect [":copy"] <address: string> */
124                         $this->s_ = array(
125                                 'valid_after' => str_replace('(', '(script-start|', $this->nonTestCommands_),
126                                 'arguments' => array(
127                                         array('class' => 'tag', 'occurrences' => '?', 'values' => array(
128                                                 array('occurrences' => '?', 'regex' => ':copy', 'requires' => 'copy', 'name' => 'size-type')
129                                         )),
130                                         array('class' => 'string', 'occurrences' => '1', 'values' => array(
131                                                 array('occurrences' => '1', 'regex' => '".*"', 'name' => 'address')
132                                         ))
133                                 )
134                         );
135                         break;
137                 case 'reject':
138                         /* reject <reason: string> */
139                         $this->s_ = array(
140                                 'requires' => 'reject',
141                                 'valid_after' => $this->nonTestCommands_,
142                                 'arguments' => array(
143                                         array('class' => 'string', 'occurrences' => '1', 'values' => array(
144                                                 array('occurrences' => '1', 'regex' => '("|).*("|)', 'name' => 'reason')
145                                         ))
146                                 )
147                         );
148                         break;
150                 case 'setflag':
151                 case 'addflag':
152                 case 'removeflag':
153                         /* setflag <flag-list: string-list> */
154                         /* addflag <flag-list: string-list> */
155                         /* removeflag <flag-list: string-list> */
156                         $this->s_ = array(
157                                 'requires' => 'imapflags',
158                                 'valid_after' =>$this->nonTestCommands_,
159                                 'arguments' => array(
160                                         array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
161                                                 array('occurrences' => '+', 'regex' => '".*"', 'name' => 'key')
162                                         ))
163                                 )
164                         );
165                         break;
167                 case 'vacation':
168                         /* vacation [":days" number] [":addresses" string-list] [":subject" string] [":mime"] <reason: string> */
169                         $this->s_ = array(
170                                 'requires' => 'vacation',
171                                 'valid_after' => $this->nonTestCommands_,
172                                 'arguments' => array(
173                                         array('class' => 'tag', 'occurrences' => '*', 'values' => array(
174                                                 array('occurrences' => '?', 'regex' => ':days', 'name' => 'days',
175                                                         'add' => array(
176                                                                 array('class' => 'number', 'occurrences' => '1', 'values' => array(
177                                                                         array('occurrences' => '1', 'regex' => '.*', 'name' => 'period')
178                                                                 ))
179                                                         )
180                                                 ),
181                                                 array('occurrences' => '?', 'regex' => ':addresses', 'name' => 'addresses',
182                                                         'add' => array(
183                                                                 array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
184                                                                         array('occurrences' => '+', 'regex' => '".*"', 'name' => 'address')
185                                                                 ))
186                                                         )
187                                                 ),
188                                                 array('occurrences' => '?', 'regex' => ':subject', 'name' => 'subject',
189                                                         'add' => array(
190                                                                 array('class' => 'string', 'occurrences' => '1', 'values' => array(
191                                                                         array('occurrences' => '1', 'regex' => '".*"', 'name' => 'subject')
192                                                                 ))
193                                                         )
194                                                 ),
195                                                 array('occurrences' => '?', 'regex' => ':mime', 'name' => 'mime')
196                                         )),
197                                         array('class' => 'string', 'occurrences' => '1', 'values' => array(
198                                                 array('occurrences' => '1', 'regex' => '.*', 'name' => 'reason')
199                                         ))
200                                 )
201                         );
202                         break;
205                 /*****************
206                  * test commands
207                  */
208                 case 'address':
209                         /* address [address-part: tag] [comparator: tag] [match-type: tag] <header-list: string-list> <key-list: string-list> */
210                         $this->s_ = array(
211                                 'valid_after' => $this->testsValidAfter_,
212                                 'arguments' => array(
213                                         array('class' => 'tag', 'occurrences' => '*', 'post-call' => 'checkTags_', 'values' => array(
214                                                 array('occurrences' => '?', 'regex' => ':(is|contains|matches|count|value|regex)', 'call' => 'setMatchType_', 'name' => 'match-type'),
215                                                 array('occurrences' => '?', 'regex' => ':(all|localpart|domain|user|detail)', 'call' => 'checkAddrPart_', 'name' => 'address-part'),
216                                                 array('occurrences' => '?', 'regex' => ':comparator', 'name' => 'comparator',
217                                                         'add' => array(
218                                                                 array('class' => 'string', 'occurrences' => '1', 'call' => 'setComparator_', 'values' => array(
219                                                                         array('occurrences' => '1', 'regex' => '"i;(octet|ascii-casemap)"', 'name' => 'comparator-string'),
220                                                                         array('occurrences' => '1', 'regex' => '"i;ascii-numeric"', 'requires' => 'comparator-i;ascii-numeric', 'name' => 'comparator-string')
221                                                                 ))
222                                                         )
223                                                 )
224                                         )),
225                                         array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
226                                                 array('occurrences' => '+', 'regex' => '".*"', 'name' => 'header')
227                                         )),
228                                         array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
229                                                 array('occurrences' => '+', 'regex' => '".*"', 'name' => 'key')
230                                         ))
231                                 )
232                         );
233                         break;
235                 case 'allof':
236                 case 'anyof':
237                         /* allof <tests: test-list>
238                            anyof <tests: test-list> */
239                         $this->s_ = array(
240                                 'valid_after' => $this->testsValidAfter_,
241                                 'arguments' => array(
242                                         array('class' => 'left-parant', 'occurrences' => '1', 'values' => array(
243                                                 array('occurrences' => '1', 'regex' => '\(', 'name' => 'test-list')
244                                         )),
245                                         array('class' => 'identifier', 'occurrences' => '+', 'values' => array(
246                                                 array('occurrences' => '+', 'regex' => $this->testCommands_, 'name' => 'test')
247                                         ))
248                                 )
249                         );
250                         break;
252                 case 'envelope':
253                         /* envelope [address-part: tag] [comparator: tag] [match-type: tag] <envelope-part: string-list> <key-list: string-list> */
254                         $this->s_ = array(
255                                 'requires' => 'envelope',
256                                 'valid_after' => $this->testsValidAfter_,
257                                 'arguments' => array(
258                                         array('class' => 'tag', 'occurrences' => '*', 'post-call' => 'checkTags_', 'values' => array(
259                                                 array('occurrences' => '?', 'regex' => ':(is|contains|matches|count|value|regex)', 'call' => 'setMatchType_', 'name' => 'match-type'),
260                                                 array('occurrences' => '?', 'regex' => ':(all|localpart|domain|user|detail)', 'call' => 'checkAddrPart_', 'name' => 'address-part'),
261                                                 array('occurrences' => '?', 'regex' => ':comparator', 'name' => 'comparator',
262                                                         'add' => array(
263                                                                 array('class' => 'string', 'occurrences' => '1', 'call' => 'setComparator_', 'values' => array(
264                                                                         array('occurrences' => '1', 'regex' => '"i;(octet|ascii-casemap)"', 'name' => 'comparator-string'),
265                                                                         array('occurrences' => '1', 'regex' => '"i;ascii-numeric"', 'requires' => 'comparator-i;ascii-numeric', 'name' => 'comparator-string')
266                                                                 ))
267                                                         )
268                                                 )
269                                         )),
270                                         array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
271                                                 array('occurrences' => '+', 'regex' => '".*"', 'name' => 'envelope-part')
272                                         )),
273                                         array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
274                                                 array('occurrences' => '+', 'regex' => '".*"', 'name' => 'key')
275                                         ))
276                                 )
277                         );
278                         break;
280                 case 'exists':
281                         /* exists <header-names: string-list> */
282                         $this->s_ = array(
283                                 'valid_after' => $this->testsValidAfter_,
284                                 'arguments' => array(
285                                         array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
286                                                 array('occurrences' => '+', 'regex' => '".*"', 'name' => 'header')
287                                         ))
288                                 )
289                         );
290                         break;
292                 case 'header':
293                         /* header [comparator: tag] [match-type: tag] <header-names: string-list> <key-list: string-list> */
294                         $this->s_ = array(
295                                 'valid_after' => $this->testsValidAfter_,
296                                 'arguments' => array(
297                                         array('class' => 'tag', 'occurrences' => '*', 'post-call' => 'checkTags_', 'values' => array(
298                                                 array('occurrences' => '?', 'regex' => ':(is|contains|matches|count|value|regex)', 'call' => 'setMatchType_', 'name' => 'match-type'),
299                                                 array('occurrences' => '?', 'regex' => ':comparator', 'name' => 'comparator',
300                                                         'add' => array(
301                                                                 array('class' => 'string', 'occurrences' => '1', 'call' => 'setComparator_', 'values' => array(
302                                                                         array('occurrences' => '1', 'regex' => '"i;(octet|ascii-casemap)"', 'name' => 'comparator-string'),
303                                                                         array('occurrences' => '1', 'regex' => '"i;ascii-numeric"', 'requires' => 'comparator-i;ascii-numeric', 'name' => 'comparator-string')
304                                                                 ))
305                                                         )
306                                                 )
307                                         )),
308                                         array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
309                                                 array('occurrences' => '+', 'regex' => '".*"', 'name' => 'header')
310                                         )),
311                                         array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
312                                                 array('occurrences' => '+', 'regex' => '".*"', 'name' => 'key')
313                                         ))
314                                 )
315                         );
316                         break;
318                 case 'not':
319                         /* not <test> */
320                         $this->s_ = array(
321                                 'valid_after' => $this->testsValidAfter_,
322                                 'arguments' => array(
323                                         array('class' => 'identifier', 'occurrences' => '1', 'values' => array(
324                                                 array('occurrences' => '1', 'regex' => $this->testCommands_, 'name' => 'test')
325                                         ))
326                                 )
327                         );
328                         break;
330                 case 'size':
331                         /* size <":over" / ":under"> <limit: number> */
332                         $this->s_ = array(
333                                 'valid_after' => $this->testsValidAfter_,
334                                 'arguments' => array(
335                                         array('class' => 'tag', 'occurrences' => '1', 'values' => array(
336                                                 array('occurrences' => '1', 'regex' => ':(over|under)', 'name' => 'size-type')
337                                         )),
338                                         array('class' => 'number', 'occurrences' => '1', 'values' => array(
339                                                 array('occurrences' => '1', 'regex' => '.*', 'name' => 'limit')
340                                         ))
341                                 )
342                         );
343                         break;
345                 case 'true':
346                 case 'false':
347                         /* true / false */
348                         $this->s_ = array(
349                                 'valid_after' => $this->testsValidAfter_
350                         );
351                         break;
354                 /********************
355                  * unknown commands
356                  */
357                 default:
358                         $this->unknown = true;
359                 }
360         }
362         function setExtensionFuncs($setFn, $checkFn)
363         {
364                 if (is_callable($setFn) && is_callable($checkFn))
365                 {
366                         $this->registerExtensionFn_ = $setFn;
367                         $this->isExtensionRegisteredFn_ = $checkFn;
368                 }
369         }
371         function setRequire_($extension)
372         {
373                 call_user_func($this->registerExtensionFn_, $extension);
374                 return true;
375         }
377         function wasRequired_($extension)
378         {
379                 return call_user_func($this->isExtensionRegisteredFn_, $extension);
380         }
382         function setMatchType_($text)
383         {
384                 // Do special processing for relational test extension
385                 if ($text == ':count' || $text == ':value')
386                 {
387                         if (!$this->wasRequired_('relational'))
388                         {
389                                 $this->message = 'missing require for match-type '. $text;
390                                 return false;
391                         }
393                         array_unshift($this->s_['arguments'],
394                                 array('class' => 'string', 'occurrences' => '1', 'values' => array(
395                                         array('occurrences' => '1', 'regex' => '"(lt|le|eq|ge|gt|ne)"', 'name' => 'relation-string'),
396                                 ))
397                         );
398                 }
399                 // Do special processing for regex match-type extension
400                 else if ($text == ':regex' && !$this->wasRequired_('regex'))
401                 {
402                         $this->message = 'missing require for match-type '. $text;
403                         return false;
404                 }
405                 $this->matchType_ = $text;
406                 return true;
407         }
409         function setComparator_($text)
410         {
411                 $this->comparator_ = $text;
412                 return true;
413         }
415         function checkAddrPart_($text)
416         {
417                 if ($text == ':user' || $text == ':detail')
418                 {
419                         if (!$this->wasRequired_('subaddress'))
420                         {
421                                 $this->message = 'missing require for tag '. $text;
422                                 return false;
423                         }
424                 }
425                 return true;
426         }
428         function checkTags_()
429         {
430                 if (isset($this->matchType_) &&
431                     $this->matchType_ == ':count' &&
432                     $this->comparator_ != '"i;ascii-numeric"')
433                 {
434                         $this->message = 'match-type :count needs comparator i;ascii-numeric';
435                         return false;
436                 }
437                 return true;
438         }
440         function validCommand($prev, $line)
441         {
442                 // Check if command is known
443                 if ($this->unknown)
444                 {
445                         $this->message = 'line '. $line .': unknown command "'. $this->command_ .'"';
446                         return false;
447                 }
449                 // Check if the command needs to be required
450                 if (isset($this->s_['requires']) && !$this->wasRequired_($this->s_['requires']))
451                 {
452                         $this->message = 'line '. $line .': missing require for command "'. $this->command_ .'"';
453                         return false;
454                 }
456                 // Check if command may appear here
457                 if (!ereg($this->s_['valid_after'], $prev))
458                 {
459 #                       $this->message = 'line '. $line .': "'. $this->command_ .'" may not appear after "'. $prev .'"';
460 #                       return false;
461                 }
463                 return true;
464         }
466         function validClass_($class, $id)
467         {
468                 // Check if command expects any arguments
469                 if (!isset($this->s_['arguments']))
470                 {
471                         $this->message = $id .' where semicolon expected';
472                         return false;
473                 }
475                 foreach ($this->s_['arguments'] as $arg)
476                 {
477                         if ($class == $arg['class'])
478                         {
479                                 return true;
480                         }
482                         // Is the argument required
483                         if ($arg['occurrences'] != '?' && $arg['occurrences'] != '*')
484                         {
485                                 $this->message = $id .' where '. $arg['class'] .' expected';
486                                 return false;
487                         }
489                         if (isset($arg['post-call']) &&
490                                 !call_user_func(array(&$this, $arg['post-call'])))
491                         {
492                                 return false;
493                         }
494                         array_shift($this->s_['arguments']);
495                 }
497                 $this->message = 'unexpected '. $id;
498                 return false;
499         }
501         function startStringList($line)
502         {
503                 if (!$this->validClass_('string', 'string'))
504                 {
505                         $this->message = 'line '. $line .': '. $this->message;
506                         return false;
507                 }
508                 else if (!isset($this->s_['arguments'][0]['list']))
509                 {
510                         $this->message = 'line '. $line .': '. 'left bracket where '. $this->s_['arguments'][0]['class'] .' expected';
511                         return false;
512                 }
514                 $this->s_['arguments'][0]['occurrences'] = '+';
515                 return true;
516         }
518         function endStringList()
519         {
520                 array_shift($this->s_['arguments']);
521         }
523         function validToken($class, &$text, &$line)
524         {
525                 $name = $class . ($class != $text ? " $text" : '');
527                 // Make sure the argument has a valid class
528                 if (!$this->validClass_($class, $name))
529                 {
530                         $this->message = 'line '. $line .': '. $this->message;
531                         return false;
532                 }
534                 $arg = &$this->s_['arguments'][0];
535                 foreach ($arg['values'] as $val)
536                 {
537                         if (preg_match('/^'. $val['regex'] .'$/m', $text))
538                         {
539                                 // Check if the argument value needs a 'require'
540                                 if (isset($val['requires']) && !$this->wasRequired_($val['requires']))
541                                 {
542                                         $this->message = 'line '. $line .': missing require for '. $val['name'] .' '. $text;
543                                         return false;
544                                 }
546                                 // Check if a possible value of this argument may occur
547                                 if ($val['occurrences'] == '?' || $val['occurrences'] == '1')
548                                 {
549                                         $val['occurrences'] = '0';
550                                 }
551                                 else if ($val['occurrences'] == '+')
552                                 {
553                                         $val['occurrences'] = '*';
554                                 }
555                                 else if ($val['occurrences'] == '0')
556                                 {
557                                         $this->message = 'line '. $line .': too many '. $val['name'] .' '. $class .'s near '. $text;
558                                         return false;
559                                 }
561                                 // Call extra processing function if defined
562                                 if (isset($val['call']) && !call_user_func(array(&$this, $val['call']), $text) ||
563                                         isset($arg['call']) && !call_user_func(array(&$this, $arg['call']), $text))
564                                 {
565                                         $this->message = 'line '. $line .': '. $this->message;
566                                         return false;
567                                 }
569                                 // Set occurrences appropriately
570                                 if ($arg['occurrences'] == '?' || $arg['occurrences'] == '1')
571                                 {
572                                         array_shift($this->s_['arguments']);
573                                 }
574                                 else
575                                 {
576                                         $arg['occurrences'] = '*';
577                                 }
579                                 // Add argument(s) expected to follow right after this one
580                                 if (isset($val['add']))
581                                 {
582                                         while ($add_arg = array_pop($val['add']))
583                                         {
584                                                 array_unshift($this->s_['arguments'], $add_arg);
585                                         }
586                                 }
588                                 return true;
589                         }
590                 }
592                 $this->message = 'line '. $line .': unexpected '. $name;
593                 return false;
594         }
596         function done($class, $text, $line)
597         {
598                 if (isset($this->s_['arguments']))
599                 {
600                         foreach ($this->s_['arguments'] as $arg)
601                         {
602                                 if ($arg['occurrences'] == '+' || $arg['occurrences'] == '1')
603                                 {
604                                         $this->message = 'line '. $line .': '. $class .' '. $text .' where '. $arg['class'] .' expected';
605                                         return false;
606                                 }
607                         }
608                 }
609                 return true;
610         }
613 ?>