Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403]

   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  }