Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 401] [Versions 310 and 402] [Versions 310 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      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
 237       *                close paren/EOS), Horde_Stream object, or null.
 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;
 250              $in_quote = $text = $binary = false;
 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
 294                          // binary-safe. But keep it if it is not used as string
 295                          // identifier.
 296                          $binary = true;
 297                          $text .= $c;
 298                          continue 3;
 299  
 300                      case '{':
 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                          }
 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                  }
 339                  $binary = false;
 340              }
 341  
 342              if ($check_len) {
 343                  switch (strlen($text)) {
 344                  case 0:
 345                      $text = false;
 346                      break;
 347  
 348                  case 3:
 349                      if (strcasecmp($text, 'NIL') === 0) {
 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  
 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  
 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  }