PageRenderTime 25ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/horde/framework/Horde/Imap/Client/Tokenize.php

https://gitlab.com/unofficial-mirrors/moodle
PHP | 413 lines | 270 code | 27 blank | 116 comment | 10 complexity | f270422e6d485b5c5e2a65adeca83737 MD5 | raw file
  1. <?php
  2. /**
  3. * Copyright 2012-2017 Horde LLC (http://www.horde.org/)
  4. *
  5. * See the enclosed file COPYING 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. * Tokenization of an IMAP data stream.
  15. *
  16. * NOTE: This class is NOT intended to be accessed outside of this package.
  17. * There is NO guarantees that the API of this class will not change across
  18. * versions.
  19. *
  20. * @author Michael Slusarz <slusarz@horde.org>
  21. * @category Horde
  22. * @copyright 2012-2017 Horde LLC
  23. * @internal
  24. * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
  25. * @package Imap_Client
  26. *
  27. * @property-read boolean $eos Has the end of the stream been reached?
  28. */
  29. class Horde_Imap_Client_Tokenize implements Iterator
  30. {
  31. /**
  32. * Current data.
  33. *
  34. * @var mixed
  35. */
  36. protected $_current = false;
  37. /**
  38. * Current key.
  39. *
  40. * @var integer
  41. */
  42. protected $_key = false;
  43. /**
  44. * Sublevel.
  45. *
  46. * @var integer
  47. */
  48. protected $_level = false;
  49. /**
  50. * Array of literal stream objects.
  51. *
  52. * @var array
  53. */
  54. protected $_literals = array();
  55. /**
  56. * Return Horde_Stream object for literal tokens?
  57. *
  58. * @var boolean
  59. */
  60. protected $_literalStream = false;
  61. /**
  62. * next() modifiers.
  63. *
  64. * @var array
  65. */
  66. protected $_nextModify = array();
  67. /**
  68. * Data stream.
  69. *
  70. * @var Horde_Stream
  71. */
  72. protected $_stream;
  73. /**
  74. * Constructor.
  75. *
  76. * @param mixed $data Data to add (string, resource, or Horde_Stream
  77. * object).
  78. */
  79. public function __construct($data = null)
  80. {
  81. $this->_stream = new Horde_Stream_Temp();
  82. if (!is_null($data)) {
  83. $this->add($data);
  84. }
  85. }
  86. /**
  87. */
  88. public function __clone()
  89. {
  90. throw new LogicException('Object can not be cloned.');
  91. }
  92. /**
  93. */
  94. public function __get($name)
  95. {
  96. switch ($name) {
  97. case 'eos':
  98. return $this->_stream->eof();
  99. }
  100. }
  101. /**
  102. */
  103. public function __sleep()
  104. {
  105. throw new LogicException('Object can not be serialized.');
  106. }
  107. /**
  108. */
  109. public function __toString()
  110. {
  111. $pos = $this->_stream->pos();
  112. $out = $this->_current . ' ' . $this->_stream->getString();
  113. $this->_stream->seek($pos, false);
  114. return $out;
  115. }
  116. /**
  117. * Add data to buffer.
  118. *
  119. * @param mixed $data Data to add (string, resource, or Horde_Stream
  120. * object).
  121. */
  122. public function add($data)
  123. {
  124. $this->_stream->add($data);
  125. }
  126. /**
  127. * Add data to literal stream at the current position.
  128. *
  129. * @param mixed $data Data to add (string, resource, or Horde_Stream
  130. * object).
  131. */
  132. public function addLiteralStream($data)
  133. {
  134. $pos = $this->_stream->pos();
  135. if (!isset($this->_literals[$pos])) {
  136. $this->_literals[$pos] = new Horde_Stream_Temp();
  137. }
  138. $this->_literals[$pos]->add($data);
  139. }
  140. /**
  141. * Flush the remaining entries left in the iterator.
  142. *
  143. * @param boolean $return If true, return entries. Only returns entries
  144. * on the current level.
  145. * @param boolean $sublevel Only flush items in current sublevel?
  146. *
  147. * @return array The entries if $return is true.
  148. */
  149. public function flushIterator($return = true, $sublevel = true)
  150. {
  151. $out = array();
  152. if ($return) {
  153. $this->_nextModify = array(
  154. 'level' => $sublevel ? $this->_level : 0,
  155. 'out' => array()
  156. );
  157. $this->next();
  158. $out = $this->_nextModify['out'];
  159. $this->_nextModify = array();
  160. } elseif ($sublevel && $this->_level) {
  161. $this->_nextModify = array(
  162. 'level' => $this->_level
  163. );
  164. $this->next();
  165. $this->_nextModify = array();
  166. } else {
  167. $this->_stream->end();
  168. $this->_stream->getChar();
  169. $this->_current = $this->_key = $this->_level = false;
  170. }
  171. return $out;
  172. }
  173. /**
  174. * Return literal length data located at the end of the stream.
  175. *
  176. * @return mixed Null if no literal data found, or an array with these
  177. * keys:
  178. * - binary: (boolean) True if this is a literal8.
  179. * - length: (integer) Length of the literal.
  180. */
  181. public function getLiteralLength()
  182. {
  183. if ($this->_stream->substring(-1, 1) === '}') {
  184. $literal_data = $this->_stream->getString(
  185. $this->_stream->search('{', true) - 1
  186. );
  187. $literal_len = substr($literal_data, 2, -1);
  188. if (is_numeric($literal_len)) {
  189. return array(
  190. 'binary' => ($literal_data[0] === '~'),
  191. 'length' => intval($literal_len)
  192. );
  193. }
  194. }
  195. return null;
  196. }
  197. /* Iterator methods. */
  198. /**
  199. */
  200. public function current()
  201. {
  202. return $this->_current;
  203. }
  204. /**
  205. */
  206. public function key()
  207. {
  208. return $this->_key;
  209. }
  210. /**
  211. * @return mixed Either a string, boolean (true for open paren, false for
  212. * close paren/EOS), Horde_Stream object, or null.
  213. */
  214. public function next()
  215. {
  216. $level = isset($this->_nextModify['level'])
  217. ? $this->_nextModify['level']
  218. : null;
  219. /* Directly access stream here to drastically reduce the number of
  220. * getChar() calls we would have to make. */
  221. $stream = $this->_stream->stream;
  222. do {
  223. $check_len = true;
  224. $in_quote = $text = $binary = false;
  225. while (($c = fgetc($stream)) !== false) {
  226. switch ($c) {
  227. case '\\':
  228. $text .= $in_quote
  229. ? fgetc($stream)
  230. : $c;
  231. break;
  232. case '"':
  233. if ($in_quote) {
  234. $check_len = false;
  235. break 2;
  236. }
  237. $in_quote = true;
  238. /* Set $text to non-false (could be empty string). */
  239. $text = '';
  240. break;
  241. default:
  242. if ($in_quote) {
  243. $text .= $c;
  244. break;
  245. }
  246. switch ($c) {
  247. case '(':
  248. ++$this->_level;
  249. $check_len = false;
  250. $text = true;
  251. break 3;
  252. case ')':
  253. if ($text === false) {
  254. --$this->_level;
  255. $check_len = $text = false;
  256. } else {
  257. $this->_stream->seek(-1);
  258. }
  259. break 3;
  260. case '~':
  261. // Ignore binary string identifier. PHP strings are
  262. // binary-safe. But keep it if it is not used as string
  263. // identifier.
  264. $binary = true;
  265. $text .= $c;
  266. continue;
  267. case '{':
  268. if ($binary) {
  269. $text = substr($text, 0, -1);
  270. }
  271. $literal_len = intval($this->_stream->getToChar('}'));
  272. $pos = $this->_stream->pos();
  273. if (isset($this->_literals[$pos])) {
  274. $text = $this->_literals[$pos];
  275. if (!$this->_literalStream) {
  276. $text = strval($text);
  277. }
  278. } elseif ($this->_literalStream) {
  279. $text = new Horde_Stream_Temp();
  280. while (($literal_len > 0) && !feof($stream)) {
  281. $part = $this->_stream->substring(
  282. 0,
  283. min($literal_len, 8192)
  284. );
  285. $text->add($part);
  286. $literal_len -= strlen($part);
  287. }
  288. } else {
  289. $text = $this->_stream->substring(0, $literal_len);
  290. }
  291. $check_len = false;
  292. break 3;
  293. case ' ':
  294. if ($text !== false) {
  295. break 3;
  296. }
  297. break;
  298. default:
  299. $text .= $c;
  300. break;
  301. }
  302. break;
  303. }
  304. $binary = false;
  305. }
  306. if ($check_len) {
  307. switch (strlen($text)) {
  308. case 0:
  309. $text = false;
  310. break;
  311. case 3:
  312. if (strcasecmp($text, 'NIL') === 0) {
  313. $text = null;
  314. }
  315. break;
  316. }
  317. }
  318. if (($text === false) && feof($stream)) {
  319. $this->_key = $this->_level = false;
  320. break;
  321. }
  322. ++$this->_key;
  323. if (is_null($level) || ($level > $this->_level)) {
  324. break;
  325. }
  326. if (($level === $this->_level) && !is_bool($text)) {
  327. $this->_nextModify['out'][] = $text;
  328. }
  329. } while (true);
  330. $this->_current = $text;
  331. return $text;
  332. }
  333. /**
  334. * Force return of literal data as stream, if next token.
  335. *
  336. * @see next()
  337. */
  338. public function nextStream()
  339. {
  340. $changed = $this->_literalStream;
  341. $this->_literalStream = true;
  342. $out = $this->next();
  343. if ($changed) {
  344. $this->_literalStream = false;
  345. }
  346. return $out;
  347. }
  348. /**
  349. */
  350. public function rewind()
  351. {
  352. $this->_stream->rewind();
  353. $this->_current = false;
  354. $this->_key = -1;
  355. $this->_level = 0;
  356. }
  357. /**
  358. */
  359. public function valid()
  360. {
  361. return ($this->_level !== false);
  362. }
  363. }