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.
<?php
/**
 * Copyright 2012-2017 Horde LLC (http://www.horde.org/)
 *
 * See the enclosed file LICENSE for license information (LGPL). If you
 * did not receive this file, see http://www.horde.org/licenses/lgpl21.
 *
 * @category  Horde
 * @copyright 2012-2017 Horde LLC
 * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
 * @package   Imap_Client
 */

/**
 * Tokenization of an IMAP data stream.
 *
 * NOTE: This class is NOT intended to be accessed outside of this package.
 * There is NO guarantees that the API of this class will not change across
 * versions.
 *
 * @author    Michael Slusarz <slusarz@horde.org>
 * @category  Horde
 * @copyright 2012-2017 Horde LLC
 * @internal
 * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
 * @package   Imap_Client
 *
 * @property-read boolean $eos  Has the end of the stream been reached?
 */
class Horde_Imap_Client_Tokenize implements Iterator
{
    /**
     * Current data.
     *
     * @var mixed
     */
    protected $_current = false;

    /**
     * Current key.
     *
     * @var integer
     */
    protected $_key = false;

    /**
     * Sublevel.
     *
     * @var integer
     */
    protected $_level = false;

    /**
     * Array of literal stream objects.
     *
     * @var array
     */
    protected $_literals = array();

    /**
     * Return Horde_Stream object for literal tokens?
     *
     * @var boolean
     */
    protected $_literalStream = false;

    /**
     * next() modifiers.
     *
     * @var array
     */
    protected $_nextModify = array();

    /**
     * Data stream.
     *
     * @var Horde_Stream
     */
    protected $_stream;

    /**
     * Constructor.
     *
     * @param mixed $data  Data to add (string, resource, or Horde_Stream
     *                     object).
     */
    public function __construct($data = null)
    {
        $this->_stream = new Horde_Stream_Temp();

        if (!is_null($data)) {
            $this->add($data);
        }
    }

    /**
     */
    public function __clone()
    {
        throw new LogicException('Object can not be cloned.');
    }

    /**
     */
    public function __get($name)
    {
        switch ($name) {
        case 'eos':
            return $this->_stream->eof();
        }
    }

    /**
     */
    public function __sleep()
    {
        throw new LogicException('Object can not be serialized.');
    }

    /**
     */
    public function __toString()
    {
        $pos = $this->_stream->pos();
        $out = $this->_current . ' ' . $this->_stream->getString();
        $this->_stream->seek($pos, false);
        return $out;
    }

    /**
     * Add data to buffer.
     *
     * @param mixed $data  Data to add (string, resource, or Horde_Stream
     *                     object).
     */
    public function add($data)
    {
        $this->_stream->add($data);
    }

    /**
     * Add data to literal stream at the current position.
     *
     * @param mixed $data  Data to add (string, resource, or Horde_Stream
     *                     object).
     */
    public function addLiteralStream($data)
    {
        $pos = $this->_stream->pos();
        if (!isset($this->_literals[$pos])) {
            $this->_literals[$pos] = new Horde_Stream_Temp();
        }
        $this->_literals[$pos]->add($data);
    }

    /**
     * Flush the remaining entries left in the iterator.
     *
     * @param boolean $return    If true, return entries. Only returns entries
     *                           on the current level.
     * @param boolean $sublevel  Only flush items in current sublevel?
     *
     * @return array  The entries if $return is true.
     */
    public function flushIterator($return = true, $sublevel = true)
    {
        $out = array();

        if ($return) {
            $this->_nextModify = array(
                'level' => $sublevel ? $this->_level : 0,
                'out' => array()
            );
            $this->next();
            $out = $this->_nextModify['out'];
            $this->_nextModify = array();
        } elseif ($sublevel && $this->_level) {
            $this->_nextModify = array(
                'level' => $this->_level
            );
            $this->next();
            $this->_nextModify = array();
        } else {
            $this->_stream->end();
            $this->_stream->getChar();
            $this->_current = $this->_key = $this->_level = false;
        }

        return $out;
    }

    /**
     * Return literal length data located at the end of the stream.
     *
     * @return mixed  Null if no literal data found, or an array with these
     *                keys:
     *   - binary: (boolean) True if this is a literal8.
     *   - length: (integer) Length of the literal.
     */
    public function getLiteralLength()
    {
        if ($this->_stream->substring(-1, 1) === '}') {
            $literal_data = $this->_stream->getString(
                $this->_stream->search('{', true) - 1
            );
            $literal_len = substr($literal_data, 2, -1);

            if (is_numeric($literal_len)) {
                return array(
                    'binary' => ($literal_data[0] === '~'),
                    'length' => intval($literal_len)
                );
            }
        }

        return null;
    }

    /* Iterator methods. */

    /**
     */
> #[ReturnTypeWillChange]
public function current() { return $this->_current; } /** */
> #[ReturnTypeWillChange]
public function key() { return $this->_key; } /** * @return mixed Either a string, boolean (true for open paren, false for * close paren/EOS), Horde_Stream object, or null. */
> #[ReturnTypeWillChange]
public function next() { $level = isset($this->_nextModify['level']) ? $this->_nextModify['level'] : null; /* Directly access stream here to drastically reduce the number of * getChar() calls we would have to make. */ $stream = $this->_stream->stream; do { $check_len = true; $in_quote = $text = $binary = false; while (($c = fgetc($stream)) !== false) { switch ($c) { case '\\': $text .= $in_quote ? fgetc($stream) : $c; break; case '"': if ($in_quote) { $check_len = false; break 2; } $in_quote = true; /* Set $text to non-false (could be empty string). */ $text = ''; break; default: if ($in_quote) { $text .= $c; break; } switch ($c) { case '(': ++$this->_level; $check_len = false; $text = true; break 3; case ')': if ($text === false) { --$this->_level; $check_len = $text = false; } else { $this->_stream->seek(-1); } break 3; case '~': // Ignore binary string identifier. PHP strings are // binary-safe. But keep it if it is not used as string // identifier. $binary = true; $text .= $c; continue 3; case '{': if ($binary) { $text = substr($text, 0, -1); } $literal_len = intval($this->_stream->getToChar('}')); $pos = $this->_stream->pos(); if (isset($this->_literals[$pos])) { $text = $this->_literals[$pos]; if (!$this->_literalStream) { $text = strval($text); } } elseif ($this->_literalStream) { $text = new Horde_Stream_Temp(); while (($literal_len > 0) && !feof($stream)) { $part = $this->_stream->substring( 0, min($literal_len, 8192) ); $text->add($part); $literal_len -= strlen($part); } } else { $text = $this->_stream->substring(0, $literal_len); } $check_len = false; break 3; case ' ': if ($text !== false) { break 3; } break; default: $text .= $c; break; } break; } $binary = false; } if ($check_len) { switch (strlen($text)) { case 0: $text = false; break; case 3: if (strcasecmp($text, 'NIL') === 0) { $text = null; } break; } } if (($text === false) && feof($stream)) { $this->_key = $this->_level = false; break; } ++$this->_key; if (is_null($level) || ($level > $this->_level)) { break; } if (($level === $this->_level) && !is_bool($text)) { $this->_nextModify['out'][] = $text; } } while (true); $this->_current = $text; return $text; } /** * Force return of literal data as stream, if next token. * * @see next() */ public function nextStream() { $changed = $this->_literalStream; $this->_literalStream = true; $out = $this->next(); if ($changed) { $this->_literalStream = false; } return $out; } /** */
> #[ReturnTypeWillChange]
public function rewind() { $this->_stream->rewind(); $this->_current = false; $this->_key = -1; $this->_level = 0; } /** */
> #[ReturnTypeWillChange]
public function valid() { return ($this->_level !== false); } }