LDAPAccountManager/lam/lib/3rdParty/composer/pear-pear.horde.org/Horde_Imap_Client/Horde/Imap/Client/Tokenize.php

414 lines
11 KiB
PHP

<?php
/**
* Copyright 2012-2017 Horde LLC (http://www.horde.org/)
*
* See the enclosed file COPYING 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. */
/**
*/
public function current()
{
return $this->_current;
}
/**
*/
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.
*/
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;
}
/**
*/
public function rewind()
{
$this->_stream->rewind();
$this->_current = false;
$this->_key = -1;
$this->_level = 0;
}
/**
*/
public function valid()
{
return ($this->_level !== false);
}
}