Code

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