MDL-47195 libraries: Add horde IMAP from Horde 5.2.1
[moodle.git] / lib / horde / framework / Horde / Imap / Client / Tokenize.php
CommitLineData
7d1cfe4c
AN
1<?php
2/**
3 * Copyright 2012-2014 Horde LLC (http://www.horde.org/)
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
9 * @copyright 2012-2014 Horde LLC
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
23 * @copyright 2012-2014 Horde LLC
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
53 /**
54 * next() modifiers.
55 *
56 * @var array
57 */
58 protected $_nextModify = array();
59
60 /**
61 * Data stream.
62 *
63 * @var Horde_Stream
64 */
65 protected $_stream;
66
67 /**
68 * Constructor.
69 *
70 * @param mixed $data Data to add (string, resource, or Horde_Stream
71 * object).
72 */
73 public function __construct($data = null)
74 {
75 $this->_stream = new Horde_Stream_Temp();
76
77 if (!is_null($data)) {
78 $this->add($data);
79 }
80 }
81
82 /**
83 */
84 public function __get($name)
85 {
86 switch ($name) {
87 case 'eos':
88 return $this->_stream->eof();
89 }
90 }
91
92 /**
93 */
94 public function __sleep()
95 {
96 throw new LogicException('Object can not be serialized.');
97 }
98
99 /**
100 */
101 public function __toString()
102 {
103 $pos = $this->_stream->pos();
104 $out = $this->_current . ' ' . $this->_stream->getString();
105 $this->_stream->seek($pos, false);
106 return $out;
107 }
108
109 /**
110 * Add data to buffer.
111 *
112 * @param mixed $data Data to add (string, resource, or Horde_Stream
113 * object).
114 */
115 public function add($data)
116 {
117 $this->_stream->add($data);
118 }
119
120 /**
121 * Flush the remaining entries left in the iterator.
122 *
123 * @param boolean $return If true, return entries. Only returns entries
124 * on the current level.
125 * @param boolean $sublevel Only flush items in current sublevel?
126 *
127 * @return array The entries if $return is true.
128 */
129 public function flushIterator($return = true, $sublevel = true)
130 {
131 $out = array();
132
133 if ($return) {
134 $this->_nextModify = array(
135 'level' => $sublevel ? $this->_level : 0,
136 'out' => array()
137 );
138 $this->next();
139 $out = $this->_nextModify['out'];
140 $this->_nextModify = array();
141 } elseif ($sublevel && $this->_level) {
142 $this->_nextModify = array(
143 'level' => $this->_level
144 );
145 $this->next();
146 $this->_nextModify = array();
147 } else {
148 $this->_stream->end();
149 $this->_stream->getChar();
150 $this->_current = $this->_key = $this->_level = false;
151 }
152
153 return $out;
154 }
155
156 /**
157 * Return literal length data located at the end of the stream.
158 *
159 * @return mixed Null if no literal data found, or an array with these
160 * keys:
161 * - binary: (boolean) True if this is a literal8.
162 * - length: (integer) Length of the literal.
163 */
164 public function getLiteralLength()
165 {
166 $this->_stream->end(-1);
167 if ($this->_stream->peek() === '}') {
168 $literal_data = $this->_stream->getString($this->_stream->search('{', true) - 1);
169 $literal_len = substr($literal_data, 2, -1);
170
171 if (is_numeric($literal_len)) {
172 return array(
173 'binary' => ($literal_data[0] === '~'),
174 'length' => intval($literal_len)
175 );
176 }
177 }
178
179 return null;
180 }
181
182 /* Iterator methods. */
183
184 /**
185 */
186 public function current()
187 {
188 return $this->_current;
189 }
190
191 /**
192 */
193 public function key()
194 {
195 return $this->_key;
196 }
197
198 /**
199 * @return mixed Either a string, boolean (true for open paren, false for
200 * close paren/EOS), or null.
201 */
202 public function next()
203 {
204 $level = isset($this->_nextModify['level'])
205 ? $this->_nextModify['level']
206 : null;
207 /* Directly access stream here to drastically reduce the number of
208 * getChar() calls we would have to make. */
209 $stream = $this->_stream->stream;
210
211 do {
212 $check_len = true;
213 $in_quote = $text = false;
214
215 while (($c = fgetc($stream)) !== false) {
216 switch ($c) {
217 case '\\':
218 $text .= $in_quote
219 ? fgetc($stream)
220 : $c;
221 break;
222
223 case '"':
224 if ($in_quote) {
225 $check_len = false;
226 break 2;
227 }
228 $in_quote = true;
229 /* Set $text to non-false (could be empty string). */
230 $text = '';
231 break;
232
233 default:
234 if ($in_quote) {
235 $text .= $c;
236 break;
237 }
238
239 switch ($c) {
240 case '(':
241 ++$this->_level;
242 $check_len = false;
243 $text = true;
244 break 3;
245
246 case ')':
247 if ($text === false) {
248 --$this->_level;
249 $check_len = $text = false;
250 } else {
251 $this->_stream->seek(-1);
252 }
253 break 3;
254
255 case '~':
256 // Ignore binary string identifier. PHP strings are
257 // binary-safe.
258 break;
259
260 case '{':
261 $text = $this->_stream->substring(
262 0,
263 intval($this->_stream->getToChar('}'))
264 );
265 $check_len = false;
266 break 3;
267
268 case ' ':
269 if ($text !== false) {
270 break 3;
271 }
272 break;
273
274 default:
275 $text .= $c;
276 break;
277 }
278 break;
279 }
280 }
281
282 if ($check_len) {
283 switch (strlen($text)) {
284 case 0:
285 $text = false;
286 break;
287
288 case 3:
289 if (($text === 'NIL') || (strcasecmp($text, 'NIL') === 0)) {
290 $text = null;
291 }
292 break;
293 }
294 }
295
296 if (($text === false) && feof($stream)) {
297 $this->_key = $this->_level = false;
298 break;
299 }
300
301 ++$this->_key;
302
303 if (is_null($level) || ($level > $this->_level)) {
304 break;
305 }
306
307 if (($level === $this->_level) && !is_bool($text)) {
308 $this->_nextModify['out'][] = $text;
309 }
310 } while (true);
311
312 $this->_current = $text;
313
314 return $text;
315 }
316
317 /**
318 */
319 public function rewind()
320 {
321 $this->_stream->rewind();
322 $this->_current = false;
323 $this->_key = -1;
324 $this->_level = 0;
325 }
326
327 /**
328 */
329 public function valid()
330 {
331 return ($this->_level !== false);
332 }
333
334}