See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]
1 <?php 2 /** 3 * Copyright 2012-2017 Horde LLC (http://www.horde.org/) 4 * 5 * See the enclosed file LICENSE 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-2017 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-2017 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 */ 30 class 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 * 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 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 96 /** 97 */ 98 public function __clone() 99 { 100 throw new LogicException('Object can not be cloned.'); 101 } 102 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 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 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 { 202 if ($this->_stream->substring(-1, 1) === '}') { 203 $literal_data = $this->_stream->getString( 204 $this->_stream->search('{', true) - 1 205 ); 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 #[ReturnTypeWillChange] 224 public function current() 225 { 226 return $this->_current; 227 } 228 229 /** 230 */ 231 #[ReturnTypeWillChange] 232 public function key() 233 { 234 return $this->_key; 235 } 236 237 /** 238 * @return mixed Either a string, boolean (true for open paren, false for 239 * close paren/EOS), Horde_Stream object, or null. 240 */ 241 #[ReturnTypeWillChange] 242 public function next() 243 { 244 $level = isset($this->_nextModify['level']) 245 ? $this->_nextModify['level'] 246 : null; 247 /* Directly access stream here to drastically reduce the number of 248 * getChar() calls we would have to make. */ 249 $stream = $this->_stream->stream; 250 251 do { 252 $check_len = true; 253 $in_quote = $text = $binary = false; 254 255 while (($c = fgetc($stream)) !== false) { 256 switch ($c) { 257 case '\\': 258 $text .= $in_quote 259 ? fgetc($stream) 260 : $c; 261 break; 262 263 case '"': 264 if ($in_quote) { 265 $check_len = false; 266 break 2; 267 } 268 $in_quote = true; 269 /* Set $text to non-false (could be empty string). */ 270 $text = ''; 271 break; 272 273 default: 274 if ($in_quote) { 275 $text .= $c; 276 break; 277 } 278 279 switch ($c) { 280 case '(': 281 ++$this->_level; 282 $check_len = false; 283 $text = true; 284 break 3; 285 286 case ')': 287 if ($text === false) { 288 --$this->_level; 289 $check_len = $text = false; 290 } else { 291 $this->_stream->seek(-1); 292 } 293 break 3; 294 295 case '~': 296 // Ignore binary string identifier. PHP strings are 297 // binary-safe. But keep it if it is not used as string 298 // identifier. 299 $binary = true; 300 $text .= $c; 301 continue 3; 302 303 case '{': 304 if ($binary) { 305 $text = substr($text, 0, -1); 306 } 307 $literal_len = intval($this->_stream->getToChar('}')); 308 $pos = $this->_stream->pos(); 309 if (isset($this->_literals[$pos])) { 310 $text = $this->_literals[$pos]; 311 if (!$this->_literalStream) { 312 $text = strval($text); 313 } 314 } elseif ($this->_literalStream) { 315 $text = new Horde_Stream_Temp(); 316 while (($literal_len > 0) && !feof($stream)) { 317 $part = $this->_stream->substring( 318 0, 319 min($literal_len, 8192) 320 ); 321 $text->add($part); 322 $literal_len -= strlen($part); 323 } 324 } else { 325 $text = $this->_stream->substring(0, $literal_len); 326 } 327 $check_len = false; 328 break 3; 329 330 case ' ': 331 if ($text !== false) { 332 break 3; 333 } 334 break; 335 336 default: 337 $text .= $c; 338 break; 339 } 340 break; 341 } 342 $binary = false; 343 } 344 345 if ($check_len) { 346 switch (strlen($text)) { 347 case 0: 348 $text = false; 349 break; 350 351 case 3: 352 if (strcasecmp($text, 'NIL') === 0) { 353 $text = null; 354 } 355 break; 356 } 357 } 358 359 if (($text === false) && feof($stream)) { 360 $this->_key = $this->_level = false; 361 break; 362 } 363 364 ++$this->_key; 365 366 if (is_null($level) || ($level > $this->_level)) { 367 break; 368 } 369 370 if (($level === $this->_level) && !is_bool($text)) { 371 $this->_nextModify['out'][] = $text; 372 } 373 } while (true); 374 375 $this->_current = $text; 376 377 return $text; 378 } 379 380 /** 381 * Force return of literal data as stream, if next token. 382 * 383 * @see next() 384 */ 385 public function nextStream() 386 { 387 $changed = $this->_literalStream; 388 $this->_literalStream = true; 389 390 $out = $this->next(); 391 392 if ($changed) { 393 $this->_literalStream = false; 394 } 395 396 return $out; 397 } 398 399 /** 400 */ 401 #[ReturnTypeWillChange] 402 public function rewind() 403 { 404 $this->_stream->rewind(); 405 $this->_current = false; 406 $this->_key = -1; 407 $this->_level = 0; 408 } 409 410 /** 411 */ 412 #[ReturnTypeWillChange] 413 public function valid() 414 { 415 return ($this->_level !== false); 416 } 417 418 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body