Code

Moved sieve stuff into special folder
[gosa.git] / include / sieve / class_semantics.inc
1 <?php
3 $requires_ = array();
5 class Semantics
6 {
7         var $command_;
8         var $comparator_;
9         var $matchType_;
10         var $s_;
11         var $unknown;
12         var $message;
13         var $testCommands_ = '(address|envelope|header|size|allof|anyof|exists|not|true|false)';
14         var $requireStrings_ = '(envelope|fileinto|reject|vacation|relational|subaddress)';
16         function Semantics($command)
17         {
18                 $this->command_ = $command;
19                 $this->unknown = false;
20                 switch ($command)
21                 {
23                 /********************
24                  * control commands
25                  */
26                 case 'require':
27                         /* require <capabilities: string-list> */
28                         $this->s_ = array(
29                                 'valid_after' => array('script-start', 'require'),
30                                 'arguments' => array(
31                                         array('class' => 'string', 'list' => true, 'name' => 'require-string', 'occurrences' => '1', 'call' => 'setRequire_', 'values' => array(
32                                                 array('occurrences' => '+', 'regex' => '"'. $this->requireStrings_ .'"'),
33                                                 array('occurrences' => '+', 'regex' => '"comparator-i;(octet|ascii-casemap|ascii-numeric)"')
34                                         ))
35                                 )
36                         );
37                         break;
39                 case 'if':
40                         /* if <test> <block> */
41                         $this->s_ = array(
42                                 'valid_after' => array('script-start', 'require', 'if', 'elsif', 'else',
43                                                        'reject', 'fileinto', 'redirect', 'stop', 'keep', 'discard'),
44                                 'arguments' => array(
45                                         array('class' => 'identifier', 'occurrences' => '1', 'values' => array(
46                                                 array('occurrences' => '1', 'regex' => $this->testCommands_, 'name' => 'test')
47                                         )),
48                                         array('class' => 'block-start', 'occurrences' => '1', 'values' => array(
49                                                 array('occurrences' => '1', 'regex' => '{', 'name' => 'block')
50                                         ))
51                                 )
52                         );
53                         break;
55                 case 'elsif':
56                         /* elsif <test> <block> */
57                         $this->s_ = array(
58                                 'valid_after' => array('if', 'elsif'),
59                                 'arguments' => array(
60                                         array('class' => 'identifier', 'occurrences' => '1', 'values' => array(
61                                                 array('occurrences' => '1', 'regex' => $this->testCommands_, 'name' => 'test')
62                                         )),
63                                         array('class' => 'block-start', 'occurrences' => '1', 'values' => array(
64                                                 array('occurrences' => '1', 'regex' => '{', 'name' => 'block')
65                                         ))
66                                 )
67                         );
68                         break;
70                 case 'else':
71                         /* else <block> */
72                         $this->s_ = array(
73                                 'valid_after' => array('if', 'elsif'),
74                                 'arguments' => array(
75                                         array('class' => 'block-start', 'occurrences' => '1', 'values' => array(
76                                                 array('occurrences' => '1', 'regex' => '{', 'name' => 'block')
77                                         ))
78                                 )
79                         );
80                         break;
83                 /*******************
84                  * action commands
85                  */
86                 case 'keep':
87                 case 'stop':
88                 case 'discard':
89                         /* keep / stop / discard */
90                         $this->s_ = array(
91                                 'valid_after' => array('script-start', 'require', 'if', 'elsif', 'else',
92                                                        'reject', 'fileinto', 'redirect', 'stop', 'keep', 'discard')
93                         );
94                         break;
96                 case 'fileinto':
97                         /* fileinto <folder: string> */
98                         $this->s_ = array(
99                                 'requires' => 'fileinto',
100                                 'valid_after' => array('require', 'if', 'elsif', 'else', 'reject', 'fileinto', 'redirect', 'stop', 'keep', 'discard'),
101                                 'arguments' => array(
102                                         array('class' => 'string', 'occurrences' => '1', 'values' => array(
103                                                 array('occurrences' => '1', 'regex' => '".*"', 'name' => 'folder')
104                                         ))
105                                 )
106                         );
107                         break;
109                 case 'redirect':
110                         /* redirect <address: string> */
111                         $this->s_ = array(
112                                 'valid_after' => array('script-start', 'require', 'if', 'elsif', 'else', 'reject', 'fileinto', 'redirect', 'stop', 'keep', 'discard'),
113                                 'arguments' => array(
114                                         array('class' => 'string', 'occurrences' => '1', 'values' => array(
115                                                 array('occurrences' => '1', 'regex' => '".*"', 'name' => 'address')
116                                         ))
117                                 )
118                         );
119                         break;
121                 case 'reject':
122                         /* reject <reason: string> */
123                         $this->s_ = array(
124                                 'requires' => 'reject',
125                                 'valid_after' => array('require', 'if', 'elsif', 'else', 'reject', 'fileinto', 'redirect', 'stop', 'keep', 'discard'),
126                                 'arguments' => array(
127                                         array('class' => 'string', 'occurrences' => '1', 'values' => array(
128                                                 array('occurrences' => '1', 'regex' => '".*"', 'name' => 'reason')
129                                         ))
130                                 )
131                         );
132                         break;
134                 case 'vacation':
135                         /* vacation [":days" number] [":addresses" string-list] [":subject" string] [":mime"] <reason: string> */
136                         $this->s_ = array(
137                                 'requires' => 'vacation',
138                                 'valid_after' => array('require', 'if', 'elsif', 'else', 'reject', 'fileinto', 'redirect', 'stop', 'keep', 'discard'),
139                                 'arguments' => array(
140                                         array('class' => 'tag', 'occurrences' => '*', 'values' => array(
141                                                 array('occurrences' => '?', 'regex' => ':days', 'name' => 'days',
142                                                         'add' => array(
143                                                                 array('class' => 'number', 'occurrences' => '1', 'values' => array(
144                                                                         array('occurrences' => '1', 'regex' => '.*', 'name' => 'period')
145                                                                 ))
146                                                         )
147                                                 ),
148                                                 array('occurrences' => '?', 'regex' => ':addresses', 'name' => 'addresses',
149                                                         'add' => array(
150                                                                 array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
151                                                                         array('occurrences' => '+', 'regex' => '".*"', 'name' => 'address')
152                                                                 ))
153                                                         )
154                                                 ),
155                                                 array('occurrences' => '?', 'regex' => ':subject', 'name' => 'subject',
156                                                         'add' => array(
157                                                                 array('class' => 'string', 'occurrences' => '1', 'values' => array(
158                                                                         array('occurrences' => '1', 'regex' => '".*"', 'name' => 'subject')
159                                                                 ))
160                                                         )
161                                                 ),
162                                                 array('occurrences' => '?', 'regex' => ':mime', 'name' => 'mime')
163                                         )),
164                                         array('class' => 'string', 'occurrences' => '1', 'values' => array(
165                                                 array('occurrences' => '1', 'regex' => '".*"', 'name' => 'reason')
166                                         ))
167                                 )
168                         );
169                         break;
172                 /*****************
173                  * test commands
174                  */
175                 case 'address':
176                         /* address [address-part: tag] [comparator: tag] [match-type: tag] <header-list: string-list> <key-list: string-list> */
177                         $this->s_ = array(
178                                 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'),
179                                 'arguments' => array(
180                                         array('class' => 'tag', 'occurrences' => '*', 'post-call' => 'checkTags_', 'values' => array(
181                                                 array('occurrences' => '?', 'regex' => ':(is|contains|matches|count|value)', 'call' => 'setMatchType_', 'name' => 'match-type'),
182                                                 array('occurrences' => '?', 'regex' => ':(all|localpart|domain|user|detail)', 'call' => 'checkAddrPart_', 'name' => 'address-part'),
183                                                 array('occurrences' => '?', 'regex' => ':comparator', 'name' => 'comparator',
184                                                         'add' => array(
185                                                                 array('class' => 'string', 'occurrences' => '1', 'call' => 'setComparator_', 'values' => array(
186                                                                         array('occurrences' => '1', 'regex' => '"i;(octet|ascii-casemap)"', 'name' => 'comparator-string'),
187                                                                         array('occurrences' => '1', 'regex' => '"i;ascii-numeric"', 'requires' => 'comparator-i;ascii-numeric', 'name' => 'comparator-string')
188                                                                 ))
189                                                         )
190                                                 )
191                                         )),
192                                         array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
193                                                 array('occurrences' => '+', 'regex' => '".*"', 'name' => 'header')
194                                         )),
195                                         array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
196                                                 array('occurrences' => '+', 'regex' => '".*"', 'name' => 'key')
197                                         ))
198                                 )
199                         );
200                         break;
202                 case 'allof':
203                 case 'anyof':
204                         /* allof <tests: test-list>
205                            anyof <tests: test-list> */
206                         $this->s_ = array(
207                                 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'),
208                                 'arguments' => array(
209                                         array('class' => 'left-parant', 'occurrences' => '1', 'values' => array(
210                                                 array('occurrences' => '1', 'regex' => '\(', 'name' => 'test-list')
211                                         )),
212                                         array('class' => 'identifier', 'occurrences' => '+', 'values' => array(
213                                                 array('occurrences' => '+', 'regex' => $this->testCommands_, 'name' => 'test')
214                                         ))
215                                 )
216                         );
217                         break;
219                 case 'envelope':
220                         /* envelope [address-part: tag] [comparator: tag] [match-type: tag] <envelope-part: string-list> <key-list: string-list> */
221                         $this->s_ = array(
222                                 'requires' => 'envelope',
223                                 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'),
224                                 'arguments' => array(
225                                         array('class' => 'tag', 'occurrences' => '*', 'post-call' => 'checkTags_', 'values' => array(
226                                                 array('occurrences' => '?', 'regex' => ':(is|contains|matches|count|value)', 'call' => 'setMatchType_', 'name' => 'match-type'),
227                                                 array('occurrences' => '?', 'regex' => ':(all|localpart|domain|user|detail)', 'call' => 'checkAddrPart_', 'name' => 'address-part'),
228                                                 array('occurrences' => '?', 'regex' => ':comparator', 'name' => 'comparator',
229                                                         'add' => array(
230                                                                 array('class' => 'string', 'occurrences' => '1', 'call' => 'setComparator_', 'values' => array(
231                                                                         array('occurrences' => '1', 'regex' => '"i;(octet|ascii-casemap)"', 'name' => 'comparator-string'),
232                                                                         array('occurrences' => '1', 'regex' => '"i;ascii-numeric"', 'requires' => 'comparator-i;ascii-numeric', 'name' => 'comparator-string')
233                                                                 ))
234                                                         )
235                                                 )
236                                         )),
237                                         array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
238                                                 array('occurrences' => '+', 'regex' => '".*"', 'name' => 'envelope-part')
239                                         )),
240                                         array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
241                                                 array('occurrences' => '+', 'regex' => '".*"', 'name' => 'key')
242                                         ))
243                                 )
244                         );
245                         break;
247                 case 'exists':
248                         /* exists <header-names: string-list> */
249                         $this->s_ = array(
250                                 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'),
251                                 'arguments' => array(
252                                         array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
253                                                 array('occurrences' => '+', 'regex' => '".*"', 'name' => 'header')
254                                         ))
255                                 )
256                         );
257                         break;
259                 case 'header':
260                         /* header [comparator: tag] [match-type: tag] <header-names: string-list> <key-list: string-list> */
261                         $this->s_ = array(
262                                 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'),
263                                 'arguments' => array(
264                                         array('class' => 'tag', 'occurrences' => '*', 'post-call' => 'checkTags_', 'values' => array(
265                                                 array('occurrences' => '?', 'regex' => ':(is|contains|matches|count|value)', 'call' => 'setMatchType_', 'name' => 'match-type'),
266                                                 array('occurrences' => '?', 'regex' => ':comparator', 'name' => 'comparator',
267                                                         'add' => array(
268                                                                 array('class' => 'string', 'occurrences' => '1', 'call' => 'setComparator_', 'values' => array(
269                                                                         array('occurrences' => '1', 'regex' => '"i;(octet|ascii-casemap)"', 'name' => 'comparator-string'),
270                                                                         array('occurrences' => '1', 'regex' => '"i;ascii-numeric"', 'requires' => 'comparator-i;ascii-numeric', 'name' => 'comparator-string')
271                                                                 ))
272                                                         )
273                                                 )
274                                         )),
275                                         array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
276                                                 array('occurrences' => '+', 'regex' => '".*"', 'name' => 'header')
277                                         )),
278                                         array('class' => 'string', 'list' => true, 'occurrences' => '1', 'values' => array(
279                                                 array('occurrences' => '+', 'regex' => '".*"', 'name' => 'key')
280                                         ))
281                                 )
282                         );
283                         break;
285                 case 'not':
286                         /* not <test> */
287                         $this->s_ = array(
288                                 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'),
289                                 'arguments' => array(
290                                         array('class' => 'identifier', 'occurrences' => '1', 'values' => array(
291                                                 array('occurrences' => '1', 'regex' => $this->testCommands_, 'name' => 'test')
292                                         ))
293                                 )
294                         );
295                         break;
297                 case 'size':
298                         /* size <":over" / ":under"> <limit: number> */
299                         $this->s_ = array(
300                                 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not'),
301                                 'arguments' => array(
302                                         array('class' => 'tag', 'occurrences' => '1', 'values' => array(
303                                                 array('occurrences' => '1', 'regex' => ':(over|under)', 'name' => 'size-type')
304                                         )),
305                                         array('class' => 'number', 'occurrences' => '1', 'values' => array(
306                                                 array('occurrences' => '1', 'regex' => '.*', 'name' => 'limit')
307                                         ))
308                                 )
309                         );
310                         break;
312                 case 'true':
313                 case 'false':
314                         /* true / false */
315                         $this->s_ = array(
316                                 'valid_after' => array('if', 'elsif', 'anyof', 'allof', 'not')
317                         );
318                         break;
321                 /********************
322                  * unknown commands
323                  */
324                 default:
325                         $this->unknown = true;
326                 }
327         }
329         function setRequire_($text)
330         {
331                 global $requires_;
332                 array_push($requires_, $text);
333                 return true;
334         }
336         function setMatchType_($text)
337         {
338                 // Do special processing for relational test extension
339                 if ($text == ':count' || $text == ':value')
340                 {
341                         global $requires_;
342                         if (!in_array('"relational"', $requires_))
343                         {
344                                 $this->message = 'missing require for match-type '. $text;
345                                 return false;
346                         }
348                         array_unshift($this->s_['arguments'],
349                                 array('class' => 'string', 'occurrences' => '1', 'values' => array(
350                                         array('occurrences' => '1', 'regex' => '"(lt|le|eq|ge|gt|ne)"', 'name' => 'relation-string'),
351                                 ))
352                         );
353                 }
354                 $this->matchType_ = $text;
355                 return true;
356         }
358         function setComparator_($text)
359         {
360                 $this->comparator_ = $text;
361                 return true;
362         }
364         function checkAddrPart_($text)
365         {
366                 if ($text == ':user' || $text == ':detail')
367                 {
368                         global $requires_;
369                         if (!in_array('"subaddress"', $requires_))
370                         {
371                                 $this->message = 'missing require for tag '. $text;
372                                 return false;
373                         }
374                 }
375                 return true;
376         }
378         function checkTags_()
379         {
380                 if (isset($this->matchType_) &&
381                     $this->matchType_ == ':count' &&
382                     $this->comparator_ != '"i;ascii-numeric"')
383                 {
384                         $this->message = 'match-type :count needs comparator i;ascii-numeric';
385                         return false;
386                 }
387                 return true;
388         }
390         function validAfter($prev)
391         {
392                 return in_array($prev, $this->s_['valid_after']);
393         }
395         function validClass_($class, $id)
396         {
397                 // Check if command expects any arguments
398                 if (!isset($this->s_['arguments']))
399                 {
400                         $this->message = $id .' where semicolon expected';
401                         return false;
402                 }
404                 foreach ($this->s_['arguments'] as $arg)
405                 {
406                         if ($class == $arg['class'])
407                         {
408                                 return true;
409                         }
411                         // Is the argument required
412                         if ($arg['occurrences'] != '?' && $arg['occurrences'] != '*')
413                         {
414                                 $this->message = $id .' where '. $arg['class'] .' expected';
415                                 return false;
416                         }
418                         if (isset($arg['post-call']) &&
419                                 !call_user_func(array(&$this, $arg['post-call'])))
420                         {
421                                 return false;
422                         }
423                         array_shift($this->s_['arguments']);
424                 }
426                 $this->message = 'unexpected '. $id;
427                 return false;
428         }
430         function startStringList($line)
431         {
432                 if (!$this->validClass_('string', 'string'))
433                 {
434                         $this->message = 'line '. $line .': '. $this->message;
435                         return false;
436                 }
437                 else if (!isset($this->s_['arguments'][0]['list']))
438                 {
439                         $this->message = 'line '. $line .': '. 'left bracket where '. $this->s_['arguments'][0]['class'] .' expected';
440                         return false;
441                 }
443                 $this->s_['arguments'][0]['occurrences'] = '+';
444                 return true;
445         }
447         function endStringList()
448         {
449                 array_shift($this->s_['arguments']);
450         }
452         function validToken($class, &$text, &$line)
453         {
454                 $name = $class . ($class != $text ? " $text" : '');
456                 // Check if the command needs to be required
457                 // TODO: move this to somewhere more appropriate
458                 global $requires_;
459                 if (isset($this->s_['requires']) &&
460                     !in_array('"'.$this->s_['requires'].'"', $requires_))
461                 {
462                         $this->message = 'line '. $line .': missing require for '. $this->command_;
463                         return false;
464                 }
466                 // Make sure the argument has a valid class
467                 if (!$this->validClass_($class, $name))
468                 {
469                         $this->message = 'line '. $line .': '. $this->message;
470                         return false;
471                 }
473                 $arg = &$this->s_['arguments'][0];
474                 foreach ($arg['values'] as $val)
475                 {
476                         if (preg_match('/^'. $val['regex'] .'$/m', $text))
477                         {
478                                 // Check if the argument value needs a 'require'
479                                 if (isset($val['requires']) &&
480                                         !in_array('"'.$val['requires'].'"', $requires_))
481                                 {
482                                         $this->message = 'line '. $line .': missing require for '. $val['name'] .' '. $text;
483                                         return false;
484                                 }
486                                 // Check if a possible value of this argument may occur
487                                 if ($val['occurrences'] == '?' || $val['occurrences'] == '1')
488                                 {
489                                         $val['occurrences'] = '0';
490                                 }
491                                 else if ($val['occurrences'] == '+')
492                                 {
493                                         $val['occurrences'] = '*';
494                                 }
495                                 else if ($val['occurrences'] == '0')
496                                 {
497                                         $this->message = 'line '. $line .': too many '. $val['name'] .' '. $class .'s near '. $text;
498                                         return false;
499                                 }
501                                 // Call extra processing function if defined
502                                 if (isset($val['call']) && !call_user_func(array(&$this, $val['call']), $text) ||
503                                         isset($arg['call']) && !call_user_func(array(&$this, $arg['call']), $text))
504                                 {
505                                         $this->message = 'line '. $line .': '. $this->message;
506                                         return false;
507                                 }
509                                 // Set occurrences appropriately
510                                 if ($arg['occurrences'] == '?' || $arg['occurrences'] == '1')
511                                 {
512                                         array_shift($this->s_['arguments']);
513                                 }
514                                 else
515                                 {
516                                         $arg['occurrences'] = '*';
517                                 }
519                                 // Add argument(s) expected to follow right after this one
520                                 if (isset($val['add']))
521                                 {
522                                         while ($add_arg = array_pop($val['add']))
523                                         {
524                                                 array_unshift($this->s_['arguments'], $add_arg);
525                                         }
526                                 }
528                                 return true;
529                         }
530                 }
532                 $this->message = 'line '. $line .': unexpected '. $name;
533                 return false;
534         }
536         function done($class, $text, $line)
537         {
538                 if (isset($this->s_['arguments']))
539                 {
540                         foreach ($this->s_['arguments'] as $arg)
541                         {
542                                 if ($arg['occurrences'] == '+' || $arg['occurrences'] == '1')
543                                 {
544                                         $this->message = 'line '. $line .': '. $class .' '. $text .' where '. $arg['class'] .' expected';
545                                         return false;
546                                 }
547                         }
548                 }
549                 return true;
550         }
553 ?>