MDL-63422 lib: horde - review core loop / switch / case / continue
[moodle.git] / lib / horde / framework / Horde / Imap / Client / Tokenize.php
CommitLineData
7d1cfe4c
AN
1<?php
2/**
22793af1 3 * Copyright 2012-2017 Horde LLC (http://www.horde.org/)
7d1cfe4c
AN
4 *
5 * See the enclosed file COPYING for license information (LGPL). If you
6 * did not receive this file, see http://www.horde.org/licenses/lgpl21.
7 *
8 * @category Horde
22793af1 9 * @copyright 2012-2017 Horde LLC
7d1cfe4c
AN
10 * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
11 * @package Imap_Client
12 */
13
14/**
15 * Tokenization of an IMAP data stream.
16 *
17 * NOTE: This class is NOT intended to be accessed outside of this package.
18 * There is NO guarantees that the API of this class will not change across
19 * versions.
20 *
21 * @author Michael Slusarz <slusarz@horde.org>
22 * @category Horde
22793af1 23 * @copyright 2012-2017 Horde LLC
7d1cfe4c
AN
24 * @internal
25 * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
26 * @package Imap_Client
27 *
28 * @property-read boolean $eos Has the end of the stream been reached?
29 */
30class Horde_Imap_Client_Tokenize implements Iterator
31{
32 /**
33 * Current data.
34 *
35 * @var mixed
36 */
37 protected $_current = false;
38
39 /**
40 * Current key.
41 *
42 * @var integer
43 */
44 protected $_key = false;
45
46 /**
47 * Sublevel.
48 *
49 * @var integer
50 */
51 protected $_level = false;
52
22793af1
MG
53 /**
54 * Array of literal stream objects.
55 *
56 * @var array
57 */
58 protected $_literals = array();
59
60 /**
61 * Return Horde_Stream object for literal tokens?
62 *
63 * @var boolean
64 */
65 protected $_literalStream = false;
66
7d1cfe4c
AN
67 /**
68 * next() modifiers.
69 *
70 * @var array
71 */
72 protected $_nextModify = array();
73
74 /**
75 * Data stream.
76 *
77 * @var Horde_Stream
78 */
79 protected $_stream;
80
81 /**
82 * Constructor.
83 *
84 * @param mixed $data Data to add (string, resource, or Horde_Stream
85 * object).
86 */
87 public function __construct($data = null)
88 {
89 $this->_stream = new Horde_Stream_Temp();
90
91 if (!is_null($data)) {
92 $this->add($data);
93 }
94 }
95
22793af1
MG
96 /**
97 */
98 public function __clone()
99 {
100 throw new LogicException('Object can not be cloned.');
101 }
102
7d1cfe4c
AN
103 /**
104 */
105 public function __get($name)
106 {
107 switch ($name) {
108 case 'eos':
109 return $this->_stream->eof();
110 }
111 }
112
113 /**
114 */
115 public function __sleep()
116 {
117 throw new LogicException('Object can not be serialized.');
118 }
119
120 /**
121 */
122 public function __toString()
123 {
124 $pos = $this->_stream->pos();
125 $out = $this->_current . ' ' . $this->_stream->getString();
126 $this->_stream->seek($pos, false);
127 return $out;
128 }
129
130 /**
131 * Add data to buffer.
132 *
133 * @param mixed $data Data to add (string, resource, or Horde_Stream
134 * object).
135 */
136 public function add($data)
137 {
138 $this->_stream->add($data);
139 }
140
22793af1
MG
141 /**
142 * Add data to literal stream at the current position.
143 *
144 * @param mixed $data Data to add (string, resource, or Horde_Stream
145 * object).
146 */
147 public function addLiteralStream($data)
148 {
149 $pos = $this->_stream->pos();
150 if (!isset($this->_literals[$pos])) {
151 $this->_literals[$pos] = new Horde_Stream_Temp();
152 }
153 $this->_literals[$pos]->add($data);
154 }
155
7d1cfe4c
AN
156 /**
157 * Flush the remaining entries left in the iterator.
158 *
159 * @param boolean $return If true, return entries. Only returns entries
160 * on the current level.
161 * @param boolean $sublevel Only flush items in current sublevel?
162 *
163 * @return array The entries if $return is true.
164 */
165 public function flushIterator($return = true, $sublevel = true)
166 {
167 $out = array();
168
169 if ($return) {
170 $this->_nextModify = array(
171 'level' => $sublevel ? $this->_level : 0,
172 'out' => array()
173 );
174 $this->next();
175 $out = $this->_nextModify['out'];
176 $this->_nextModify = array();
177 } elseif ($sublevel && $this->_level) {
178 $this->_nextModify = array(
179 'level' => $this->_level
180 );
181 $this->next();
182 $this->_nextModify = array();
183 } else {
184 $this->_stream->end();
185 $this->_stream->getChar();
186 $this->_current = $this->_key = $this->_level = false;
187 }
188
189 return $out;
190 }
191
192 /**
193 * Return literal length data located at the end of the stream.
194 *
195 * @return mixed Null if no literal data found, or an array with these
196 * keys:
197 * - binary: (boolean) True if this is a literal8.
198 * - length: (integer) Length of the literal.
199 */
200 public function getLiteralLength()
201 {
22793af1
MG
202 if ($this->_stream->substring(-1, 1) === '}') {
203 $literal_data = $this->_stream->getString(
204 $this->_stream->search('{', true) - 1
205 );
7d1cfe4c
AN
206 $literal_len = substr($literal_data, 2, -1);
207
208 if (is_numeric($literal_len)) {
209 return array(
210 'binary' => ($literal_data[0] === '~'),
211 'length' => intval($literal_len)
212 );
213 }
214 }
215
216 return null;
217 }
218
219 /* Iterator methods. */
220
221 /**
222 */
223 public function current()
224 {
225 return $this->_current;
226 }
227
228 /**
229 */
230 public function key()
231 {
232 return $this->_key;
233 }
234
235 /**
236 * @return mixed Either a string, boolean (true for open paren, false for
22793af1 237 * close paren/EOS), Horde_Stream object, or null.
7d1cfe4c
AN
238 */
239 public function next()
240 {
241 $level = isset($this->_nextModify['level'])
242 ? $this->_nextModify['level']
243 : null;
244 /* Directly access stream here to drastically reduce the number of
245 * getChar() calls we would have to make. */
246 $stream = $this->_stream->stream;
247
248 do {
249 $check_len = true;
22793af1 250 $in_quote = $text = $binary = false;
7d1cfe4c
AN
251
252 while (($c = fgetc($stream)) !== false) {
253 switch ($c) {
254 case '\\':
255 $text .= $in_quote
256 ? fgetc($stream)
257 : $c;
258 break;
259
260 case '"':
261 if ($in_quote) {
262 $check_len = false;
263 break 2;
264 }
265 $in_quote = true;
266 /* Set $text to non-false (could be empty string). */
267 $text = '';
268 break;
269
270 default:
271 if ($in_quote) {
272 $text .= $c;
273 break;
274 }
275
276 switch ($c) {
277 case '(':
278 ++$this->_level;
279 $check_len = false;
280 $text = true;
281 break 3;
282
283 case ')':
284 if ($text === false) {
285 --$this->_level;
286 $check_len = $text = false;
287 } else {
288 $this->_stream->seek(-1);
289 }
290 break 3;
291
292 case '~':
293 // Ignore binary string identifier. PHP strings are
22793af1
MG
294 // binary-safe. But keep it if it is not used as string
295 // identifier.
296 $binary = true;
297 $text .= $c;
77331aed 298 break;
7d1cfe4c
AN
299
300 case '{':
22793af1
MG
301 if ($binary) {
302 $text = substr($text, 0, -1);
303 }
304 $literal_len = intval($this->_stream->getToChar('}'));
305 $pos = $this->_stream->pos();
306 if (isset($this->_literals[$pos])) {
307 $text = $this->_literals[$pos];
308 if (!$this->_literalStream) {
309 $text = strval($text);
310 }
311 } elseif ($this->_literalStream) {
312 $text = new Horde_Stream_Temp();
313 while (($literal_len > 0) && !feof($stream)) {
314 $part = $this->_stream->substring(
315 0,
316 min($literal_len, 8192)
317 );
318 $text->add($part);
319 $literal_len -= strlen($part);
320 }
321 } else {
322 $text = $this->_stream->substring(0, $literal_len);
323 }
7d1cfe4c
AN
324 $check_len = false;
325 break 3;
326
327 case ' ':
328 if ($text !== false) {
329 break 3;
330 }
331 break;
332
333 default:
334 $text .= $c;
335 break;
336 }
337 break;
338 }
22793af1 339 $binary = false;
7d1cfe4c
AN
340 }
341
342 if ($check_len) {
343 switch (strlen($text)) {
344 case 0:
345 $text = false;
346 break;
347
348 case 3:
22793af1 349 if (strcasecmp($text, 'NIL') === 0) {
7d1cfe4c
AN
350 $text = null;
351 }
352 break;
353 }
354 }
355
356 if (($text === false) && feof($stream)) {
357 $this->_key = $this->_level = false;
358 break;
359 }
360
361 ++$this->_key;
362
363 if (is_null($level) || ($level > $this->_level)) {
364 break;
365 }
366
367 if (($level === $this->_level) && !is_bool($text)) {
368 $this->_nextModify['out'][] = $text;
369 }
370 } while (true);
371
372 $this->_current = $text;
373
374 return $text;
375 }
376
22793af1
MG
377 /**
378 * Force return of literal data as stream, if next token.
379 *
380 * @see next()
381 */
382 public function nextStream()
383 {
384 $changed = $this->_literalStream;
385 $this->_literalStream = true;
386
387 $out = $this->next();
388
389 if ($changed) {
390 $this->_literalStream = false;
391 }
392
393 return $out;
394 }
395
7d1cfe4c
AN
396 /**
397 */
398 public function rewind()
399 {
400 $this->_stream->rewind();
401 $this->_current = false;
402 $this->_key = -1;
403 $this->_level = 0;
404 }
405
406 /**
407 */
408 public function valid()
409 {
410 return ($this->_level !== false);
411 }
412
413}