1 <?php
3 /*
4 requirestring: envelope, fileinto, reject, comparator-*
5 address-part: localpart, domain, all*
6 match-type: is*, contains, matches
7 comperator: i;octet, i;ascii-casemap
9 <if> <test:+> <block:1>
10 <elsif> <test:+> <block:1>
11 <else> <block:1>
12 <require> <requirestring:+>
13 <reject> <string:?>
14 <fileinto> <string:1>
15 <redirect> <string:1>
16 <stop>
17 <keep>
18 <discard>
20 <address> <address-part,comperator,match-type:?> <string:+> <string:+>
21 <envelope> <address-part,comperator,match-type:?> <string:+> <string:+>
22 <header> <comperator,match-type:?> <string:+> <string:+>
23 <size> <:over|:under:1> <number:1>
24 <allof> <test:+>
25 <anyof> <test:+>
26 <exists> <string:+>
27 <not> <test:1>
28 <true>
29 <false>
30 */
32 class SieveScript
33 {
34 var $_scanner;
35 var $_script;
36 var $_tree;
37 var $_status;
38 var $_noMoreRequires;
40 var $_test_identifier = array('address', 'allof', 'anyof', 'envelope', 'exists', 'false', 'header', 'not', 'size', 'true');
42 var $status_text;
44 function parse($script)
45 {
46 $this->status_text = "incomplete";
48 $root = 'script_start';
49 $this->_noMoreRequires = false;
50 $this->_script = $script;
51 $this->_tree = new Tree($root);
52 $this->_tree->setDumpFunc(array($this, '_dumpToken'));
53 $this->_scanner = new Scanner($this->_script);
54 $this->_scanner->setCommentFunc(array($this, '_comment'));
56 if ($this->_commands($this->_tree->getRoot()) &&
57 $this->_scanner->nextTokenIs('script-end'))
58 {
59 return $this->_success('success');
60 }
62 return $this->_status;
63 }
65 function _dumpToken(&$token)
66 {
67 if (is_array($token))
68 {
69 $str = "<" . chop(mb_substr($this->_script, $token['pos'], $token['len'])) . "> ";
70 foreach ($token as $k => $v)
71 {
72 $str .= " $k:$v";
73 }
74 return $str;
75 }
77 return strval($token);
78 }
80 function _tokenStringIs($token, $text)
81 {
82 return mb_substr($this->_script, $token['pos'], $token['len']) == $text;
83 }
85 function _success($text = null)
86 {
87 if ($text != null)
88 {
89 $this->status_text = $text;
90 }
92 return $this->_status = true;
93 }
95 function _error($text, $token = null)
96 {
97 if ($token != null)
98 {
99 $text = 'line '. $token['line'] .': '. $token['class'] . " where $text expected near ".
100 '"'. mb_substr($this->_script, $token['pos'], $token['len']) .'"';
101 }
103 $this->status_text = $text;
104 return $this->_status = false;
105 }
107 function _done()
108 {
109 return false;
110 }
112 function _comment($token)
113 {
114 $this->_tree->addChild($token);
115 }
117 function _commands($parent_id)
118 {
119 while ($this->_command($parent_id));
121 return $this->_status;
122 }
124 function _command($parent_id)
125 {
126 if (!$this->_scanner->nextTokenIs('identifier'))
127 {
128 if ($this->_scanner->nextTokenIs(array('right-curly', 'script-end')))
129 {
130 return $this->_done();
131 }
132 return $this->_error('identifier', $this->_scanner->peekNextToken());
133 }
135 // Get and check a command token
136 $token = $this->_scanner->nextToken();
137 $command = mb_substr($this->_script, $token['pos'], $token['len']);
139 if (!in_array($command, array('if', 'elsif', 'else', 'require', 'stop', 'reject', 'fileinto', 'redirect', 'keep', 'discard')))
140 {
141 return $this->_error('unknown command: '. $command);
142 }
144 if ($command != 'require')
145 {
146 $this->_noMoreRequires = true;
147 }
148 else if ($this->_noMoreRequires)
149 {
150 return $this->_error('misplaced require');
151 }
153 $this_node = $this->_tree->addChildTo($parent_id, $token);
155 if (in_array($command, array('if', 'elsif', 'require', 'reject', 'fileinto', 'redirect')))
156 {
157 // TODO: handle optional arguments in reject
158 if ($this->_arguments($this_node) == false)
159 {
160 return false;
161 }
162 }
164 $token = $this->_scanner->nextToken();
165 if (in_array($command, array('if', 'elsif', 'else')))
166 {
167 if ($token['class'] != 'left-curly')
168 {
169 return $this->_error('block', $token);
170 }
171 $this->_tree->addChildTo($this_node, $token);
172 return $this->_block($this_node);
173 }
174 else if ($token['class'] != 'semicolon')
175 {
176 return $this->_error('semicolon', $token);
177 }
179 $this->_tree->addChildTo($this_node, $token);
180 return $this->_success();
181 }
183 function _block($parent_id)
184 {
185 //TODO: test if cmd is ok w/ block
186 if ($this->_commands($parent_id))
187 {
188 $token = $this->_scanner->nextToken();
190 if ($token['class'] != 'right-curly')
191 {
192 return $this->_error('closing curly brace', $token);
193 }
195 $this->_tree->addChildTo($parent_id, $token);
196 return $this->_success();
197 }
198 return $this->_status;
199 }
201 function _arguments($parent_id)
202 {
203 while ($this->_argument($parent_id));
204 if ($this->_status == true)
205 {
206 $this->_testlist($parent_id);
207 }
208 return $this->_status;
209 }
211 function _argument($parent_id)
212 {
213 if (!$this->_scanner->nextTokenIs(array('number', 'tag')))
214 {
215 return $this->_stringlist($parent_id);
216 }
217 else
218 {
219 if ($this->_tokenStringIs($this->_tree->getNode($parent_id), 'require'))
220 {
221 return $this->_error('stringlist', $this->_scanner->nextToken());
222 }
223 }
225 $token = $this->_scanner->nextToken();
226 $this->_tree->addChildTo($parent_id, $token);
228 return $this->_success();
229 }
231 function _test($parent_id)
232 {
233 if (!$this->_scanner->nextTokenIs('identifier'))
234 {
235 return $this->_done();
236 }
238 $token = $this->_scanner->nextToken();
239 $this_node = $this->_tree->addChildTo($parent_id, $token);
241 $this->_arguments($this_node);
243 //TODO: check test for validity here
245 return $this->_status;
246 }
248 function _testlist($parent_id)
249 {
250 if (!$this->_scanner->nextTokenIs('left-parant'))
251 {
252 return $this->_test($parent_id);
253 }
255 $token = $this->_scanner->nextToken();
256 $this->_tree->addChildTo($parent_id, $token);
258 while ($token['class'] != 'right-parant')
259 {
260 if (!$this->_test($parent_id))
261 {
262 return $this->_status;
263 }
265 $token = $this->_scanner->nextToken();
267 if ($token['class'] != 'comma' && $token['class'] != 'right-parant')
268 {
269 return $this->_error('comma or closing paranthesis', $token);
270 }
272 $this->_tree->addChildTo($parent_id, $token);
273 }
275 return $this->_success();
276 }
278 function _string($parent_id)
279 {
280 if (!$this->_scanner->nextTokenIs(array('quoted-string', 'multi-line')))
281 {
282 return $this->_done();
283 }
285 $this->_tree->addChildTo($parent_id, $this->_scanner->nextToken());
287 return $this->_success();
288 }
290 function _stringlist($parent_id)
291 {
292 if (!$this->_scanner->nextTokenIs('left-bracket'))
293 {
294 return $this->_string($parent_id);
295 }
297 $token = $this->_scanner->nextToken();
298 $this->_tree->addChildTo($parent_id, $token);
300 while ($token['class'] != 'right-bracket')
301 {
302 if (!$this->_string($parent_id))
303 {
304 return $this->_status;
305 }
307 $token = $this->_scanner->nextToken();
309 if ($token['class'] != 'comma' && $token['class'] != 'right-bracket')
310 {
311 return $this->_error('comma or closing bracket', $token);
312 }
314 $this->_tree->addChildTo($parent_id, $token);
315 }
317 return $this->_success();
318 }
319 }
321 ?>