ANSI.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. <?php
  2. /**
  3. * Pure-PHP ANSI Decoder
  4. *
  5. * PHP version 5
  6. *
  7. * If you call read() in \phpseclib\Net\SSH2 you may get {@link http://en.wikipedia.org/wiki/ANSI_escape_code ANSI escape codes} back.
  8. * They'd look like chr(0x1B) . '[00m' or whatever (0x1B = ESC). They tell a
  9. * {@link http://en.wikipedia.org/wiki/Terminal_emulator terminal emulator} how to format the characters, what
  10. * color to display them in, etc. \phpseclib\File\ANSI is a {@link http://en.wikipedia.org/wiki/VT100 VT100} terminal emulator.
  11. *
  12. * @category File
  13. * @package ANSI
  14. * @author Jim Wigginton <terrafrost@php.net>
  15. * @copyright 2012 Jim Wigginton
  16. * @license http://www.opensource.org/licenses/mit-license.html MIT License
  17. * @link http://phpseclib.sourceforge.net
  18. */
  19. namespace phpseclib\File;
  20. /**
  21. * Pure-PHP ANSI Decoder
  22. *
  23. * @package ANSI
  24. * @author Jim Wigginton <terrafrost@php.net>
  25. * @access public
  26. */
  27. class ANSI
  28. {
  29. /**
  30. * Max Width
  31. *
  32. * @var int
  33. * @access private
  34. */
  35. var $max_x;
  36. /**
  37. * Max Height
  38. *
  39. * @var int
  40. * @access private
  41. */
  42. var $max_y;
  43. /**
  44. * Max History
  45. *
  46. * @var int
  47. * @access private
  48. */
  49. var $max_history;
  50. /**
  51. * History
  52. *
  53. * @var array
  54. * @access private
  55. */
  56. var $history;
  57. /**
  58. * History Attributes
  59. *
  60. * @var array
  61. * @access private
  62. */
  63. var $history_attrs;
  64. /**
  65. * Current Column
  66. *
  67. * @var int
  68. * @access private
  69. */
  70. var $x;
  71. /**
  72. * Current Row
  73. *
  74. * @var int
  75. * @access private
  76. */
  77. var $y;
  78. /**
  79. * Old Column
  80. *
  81. * @var int
  82. * @access private
  83. */
  84. var $old_x;
  85. /**
  86. * Old Row
  87. *
  88. * @var int
  89. * @access private
  90. */
  91. var $old_y;
  92. /**
  93. * An empty attribute cell
  94. *
  95. * @var object
  96. * @access private
  97. */
  98. var $base_attr_cell;
  99. /**
  100. * The current attribute cell
  101. *
  102. * @var object
  103. * @access private
  104. */
  105. var $attr_cell;
  106. /**
  107. * An empty attribute row
  108. *
  109. * @var array
  110. * @access private
  111. */
  112. var $attr_row;
  113. /**
  114. * The current screen text
  115. *
  116. * @var array
  117. * @access private
  118. */
  119. var $screen;
  120. /**
  121. * The current screen attributes
  122. *
  123. * @var array
  124. * @access private
  125. */
  126. var $attrs;
  127. /**
  128. * Current ANSI code
  129. *
  130. * @var string
  131. * @access private
  132. */
  133. var $ansi;
  134. /**
  135. * Tokenization
  136. *
  137. * @var array
  138. * @access private
  139. */
  140. var $tokenization;
  141. /**
  142. * Default Constructor.
  143. *
  144. * @return \phpseclib\File\ANSI
  145. * @access public
  146. */
  147. function __construct()
  148. {
  149. $attr_cell = new \stdClass();
  150. $attr_cell->bold = false;
  151. $attr_cell->underline = false;
  152. $attr_cell->blink = false;
  153. $attr_cell->background = 'black';
  154. $attr_cell->foreground = 'white';
  155. $attr_cell->reverse = false;
  156. $this->base_attr_cell = clone $attr_cell;
  157. $this->attr_cell = clone $attr_cell;
  158. $this->setHistory(200);
  159. $this->setDimensions(80, 24);
  160. }
  161. /**
  162. * Set terminal width and height
  163. *
  164. * Resets the screen as well
  165. *
  166. * @param int $x
  167. * @param int $y
  168. * @access public
  169. */
  170. function setDimensions($x, $y)
  171. {
  172. $this->max_x = $x - 1;
  173. $this->max_y = $y - 1;
  174. $this->x = $this->y = 0;
  175. $this->history = $this->history_attrs = array();
  176. $this->attr_row = array_fill(0, $this->max_x + 2, $this->base_attr_cell);
  177. $this->screen = array_fill(0, $this->max_y + 1, '');
  178. $this->attrs = array_fill(0, $this->max_y + 1, $this->attr_row);
  179. $this->ansi = '';
  180. }
  181. /**
  182. * Set the number of lines that should be logged past the terminal height
  183. *
  184. * @param int $x
  185. * @param int $y
  186. * @access public
  187. */
  188. function setHistory($history)
  189. {
  190. $this->max_history = $history;
  191. }
  192. /**
  193. * Load a string
  194. *
  195. * @param string $source
  196. * @access public
  197. */
  198. function loadString($source)
  199. {
  200. $this->setDimensions($this->max_x + 1, $this->max_y + 1);
  201. $this->appendString($source);
  202. }
  203. /**
  204. * Appdend a string
  205. *
  206. * @param string $source
  207. * @access public
  208. */
  209. function appendString($source)
  210. {
  211. $this->tokenization = array('');
  212. for ($i = 0; $i < strlen($source); $i++) {
  213. if (strlen($this->ansi)) {
  214. $this->ansi.= $source[$i];
  215. $chr = ord($source[$i]);
  216. // http://en.wikipedia.org/wiki/ANSI_escape_code#Sequence_elements
  217. // single character CSI's not currently supported
  218. switch (true) {
  219. case $this->ansi == "\x1B=":
  220. $this->ansi = '';
  221. continue 2;
  222. case strlen($this->ansi) == 2 && $chr >= 64 && $chr <= 95 && $chr != ord('['):
  223. case strlen($this->ansi) > 2 && $chr >= 64 && $chr <= 126:
  224. break;
  225. default:
  226. continue 2;
  227. }
  228. $this->tokenization[] = $this->ansi;
  229. $this->tokenization[] = '';
  230. // http://ascii-table.com/ansi-escape-sequences-vt-100.php
  231. switch ($this->ansi) {
  232. case "\x1B[H": // Move cursor to upper left corner
  233. $this->old_x = $this->x;
  234. $this->old_y = $this->y;
  235. $this->x = $this->y = 0;
  236. break;
  237. case "\x1B[J": // Clear screen from cursor down
  238. $this->history = array_merge($this->history, array_slice(array_splice($this->screen, $this->y + 1), 0, $this->old_y));
  239. $this->screen = array_merge($this->screen, array_fill($this->y, $this->max_y, ''));
  240. $this->history_attrs = array_merge($this->history_attrs, array_slice(array_splice($this->attrs, $this->y + 1), 0, $this->old_y));
  241. $this->attrs = array_merge($this->attrs, array_fill($this->y, $this->max_y, $this->attr_row));
  242. if (count($this->history) == $this->max_history) {
  243. array_shift($this->history);
  244. array_shift($this->history_attrs);
  245. }
  246. case "\x1B[K": // Clear screen from cursor right
  247. $this->screen[$this->y] = substr($this->screen[$this->y], 0, $this->x);
  248. array_splice($this->attrs[$this->y], $this->x + 1, $this->max_x - $this->x, array_fill($this->x, $this->max_x - $this->x - 1, $this->base_attr_cell));
  249. break;
  250. case "\x1B[2K": // Clear entire line
  251. $this->screen[$this->y] = str_repeat(' ', $this->x);
  252. $this->attrs[$this->y] = $this->attr_row;
  253. break;
  254. case "\x1B[?1h": // set cursor key to application
  255. case "\x1B[?25h": // show the cursor
  256. case "\x1B(B": // set united states g0 character set
  257. break;
  258. case "\x1BE": // Move to next line
  259. $this->_newLine();
  260. $this->x = 0;
  261. break;
  262. default:
  263. switch (true) {
  264. case preg_match('#\x1B\[(\d+)B#', $this->ansi, $match): // Move cursor down n lines
  265. $this->old_y = $this->y;
  266. $this->y+= $match[1];
  267. break;
  268. case preg_match('#\x1B\[(\d+);(\d+)H#', $this->ansi, $match): // Move cursor to screen location v,h
  269. $this->old_x = $this->x;
  270. $this->old_y = $this->y;
  271. $this->x = $match[2] - 1;
  272. $this->y = $match[1] - 1;
  273. break;
  274. case preg_match('#\x1B\[(\d+)C#', $this->ansi, $match): // Move cursor right n lines
  275. $this->old_x = $this->x;
  276. $this->x+= $match[1];
  277. break;
  278. case preg_match('#\x1B\[(\d+)D#', $this->ansi, $match): // Move cursor left n lines
  279. $this->old_x = $this->x;
  280. $this->x-= $match[1];
  281. break;
  282. case preg_match('#\x1B\[(\d+);(\d+)r#', $this->ansi, $match): // Set top and bottom lines of a window
  283. break;
  284. case preg_match('#\x1B\[(\d*(?:;\d*)*)m#', $this->ansi, $match): // character attributes
  285. $attr_cell = &$this->attr_cell;
  286. $mods = explode(';', $match[1]);
  287. foreach ($mods as $mod) {
  288. switch ($mod) {
  289. case 0: // Turn off character attributes
  290. $attr_cell = clone $this->base_attr_cell;
  291. break;
  292. case 1: // Turn bold mode on
  293. $attr_cell->bold = true;
  294. break;
  295. case 4: // Turn underline mode on
  296. $attr_cell->underline = true;
  297. break;
  298. case 5: // Turn blinking mode on
  299. $attr_cell->blink = true;
  300. break;
  301. case 7: // Turn reverse video on
  302. $attr_cell->reverse = !$attr_cell->reverse;
  303. $temp = $attr_cell->background;
  304. $attr_cell->background = $attr_cell->foreground;
  305. $attr_cell->foreground = $temp;
  306. break;
  307. default: // set colors
  308. //$front = $attr_cell->reverse ? &$attr_cell->background : &$attr_cell->foreground;
  309. $front = &$attr_cell->{ $attr_cell->reverse ? 'background' : 'foreground' };
  310. //$back = $attr_cell->reverse ? &$attr_cell->foreground : &$attr_cell->background;
  311. $back = &$attr_cell->{ $attr_cell->reverse ? 'foreground' : 'background' };
  312. switch ($mod) {
  313. // @codingStandardsIgnoreStart
  314. case 30: $front = 'black'; break;
  315. case 31: $front = 'red'; break;
  316. case 32: $front = 'green'; break;
  317. case 33: $front = 'yellow'; break;
  318. case 34: $front = 'blue'; break;
  319. case 35: $front = 'magenta'; break;
  320. case 36: $front = 'cyan'; break;
  321. case 37: $front = 'white'; break;
  322. case 40: $back = 'black'; break;
  323. case 41: $back = 'red'; break;
  324. case 42: $back = 'green'; break;
  325. case 43: $back = 'yellow'; break;
  326. case 44: $back = 'blue'; break;
  327. case 45: $back = 'magenta'; break;
  328. case 46: $back = 'cyan'; break;
  329. case 47: $back = 'white'; break;
  330. // @codingStandardsIgnoreEnd
  331. default:
  332. //user_error('Unsupported attribute: ' . $mod);
  333. $this->ansi = '';
  334. break 2;
  335. }
  336. }
  337. }
  338. break;
  339. default:
  340. //user_error("{$this->ansi} is unsupported\r\n");
  341. }
  342. }
  343. $this->ansi = '';
  344. continue;
  345. }
  346. $this->tokenization[count($this->tokenization) - 1].= $source[$i];
  347. switch ($source[$i]) {
  348. case "\r":
  349. $this->x = 0;
  350. break;
  351. case "\n":
  352. $this->_newLine();
  353. break;
  354. case "\x08": // backspace
  355. if ($this->x) {
  356. $this->x--;
  357. $this->attrs[$this->y][$this->x] = clone $this->base_attr_cell;
  358. $this->screen[$this->y] = substr_replace(
  359. $this->screen[$this->y],
  360. $source[$i],
  361. $this->x,
  362. 1
  363. );
  364. }
  365. break;
  366. case "\x0F": // shift
  367. break;
  368. case "\x1B": // start ANSI escape code
  369. $this->tokenization[count($this->tokenization) - 1] = substr($this->tokenization[count($this->tokenization) - 1], 0, -1);
  370. //if (!strlen($this->tokenization[count($this->tokenization) - 1])) {
  371. // array_pop($this->tokenization);
  372. //}
  373. $this->ansi.= "\x1B";
  374. break;
  375. default:
  376. $this->attrs[$this->y][$this->x] = clone $this->attr_cell;
  377. if ($this->x > strlen($this->screen[$this->y])) {
  378. $this->screen[$this->y] = str_repeat(' ', $this->x);
  379. }
  380. $this->screen[$this->y] = substr_replace(
  381. $this->screen[$this->y],
  382. $source[$i],
  383. $this->x,
  384. 1
  385. );
  386. if ($this->x > $this->max_x) {
  387. $this->x = 0;
  388. $this->y++;
  389. } else {
  390. $this->x++;
  391. }
  392. }
  393. }
  394. }
  395. /**
  396. * Add a new line
  397. *
  398. * Also update the $this->screen and $this->history buffers
  399. *
  400. * @access private
  401. */
  402. function _newLine()
  403. {
  404. //if ($this->y < $this->max_y) {
  405. // $this->y++;
  406. //}
  407. while ($this->y >= $this->max_y) {
  408. $this->history = array_merge($this->history, array(array_shift($this->screen)));
  409. $this->screen[] = '';
  410. $this->history_attrs = array_merge($this->history_attrs, array(array_shift($this->attrs)));
  411. $this->attrs[] = $this->attr_row;
  412. if (count($this->history) >= $this->max_history) {
  413. array_shift($this->history);
  414. array_shift($this->history_attrs);
  415. }
  416. $this->y--;
  417. }
  418. $this->y++;
  419. }
  420. /**
  421. * Returns the current coordinate without preformating
  422. *
  423. * @access private
  424. * @return string
  425. */
  426. function _processCoordinate($last_attr, $cur_attr, $char)
  427. {
  428. $output = '';
  429. if ($last_attr != $cur_attr) {
  430. $close = $open = '';
  431. if ($last_attr->foreground != $cur_attr->foreground) {
  432. if ($cur_attr->foreground != 'white') {
  433. $open.= '<span style="color: ' . $cur_attr->foreground . '">';
  434. }
  435. if ($last_attr->foreground != 'white') {
  436. $close = '</span>' . $close;
  437. }
  438. }
  439. if ($last_attr->background != $cur_attr->background) {
  440. if ($cur_attr->background != 'black') {
  441. $open.= '<span style="background: ' . $cur_attr->background . '">';
  442. }
  443. if ($last_attr->background != 'black') {
  444. $close = '</span>' . $close;
  445. }
  446. }
  447. if ($last_attr->bold != $cur_attr->bold) {
  448. if ($cur_attr->bold) {
  449. $open.= '<b>';
  450. } else {
  451. $close = '</b>' . $close;
  452. }
  453. }
  454. if ($last_attr->underline != $cur_attr->underline) {
  455. if ($cur_attr->underline) {
  456. $open.= '<u>';
  457. } else {
  458. $close = '</u>' . $close;
  459. }
  460. }
  461. if ($last_attr->blink != $cur_attr->blink) {
  462. if ($cur_attr->blink) {
  463. $open.= '<blink>';
  464. } else {
  465. $close = '</blink>' . $close;
  466. }
  467. }
  468. $output.= $close . $open;
  469. }
  470. $output.= htmlspecialchars($char);
  471. return $output;
  472. }
  473. /**
  474. * Returns the current screen without preformating
  475. *
  476. * @access private
  477. * @return string
  478. */
  479. function _getScreen()
  480. {
  481. $output = '';
  482. $last_attr = $this->base_attr_cell;
  483. for ($i = 0; $i <= $this->max_y; $i++) {
  484. for ($j = 0; $j <= $this->max_x; $j++) {
  485. $cur_attr = $this->attrs[$i][$j];
  486. $output.= $this->_processCoordinate($last_attr, $cur_attr, isset($this->screen[$i][$j]) ? $this->screen[$i][$j] : '');
  487. $last_attr = $this->attrs[$i][$j];
  488. }
  489. $output.= "\r\n";
  490. }
  491. $output = substr($output, 0, -2);
  492. // close any remaining open tags
  493. $output.= $this->_processCoordinate($last_attr, $this->base_attr_cell, '');
  494. return rtrim($output);
  495. }
  496. /**
  497. * Returns the current screen
  498. *
  499. * @access public
  500. * @return string
  501. */
  502. function getScreen()
  503. {
  504. return '<pre width="' . ($this->max_x + 1) . '" style="color: white; background: black">' . $this->_getScreen() . '</pre>';
  505. }
  506. /**
  507. * Returns the current screen and the x previous lines
  508. *
  509. * @access public
  510. * @return string
  511. */
  512. function getHistory()
  513. {
  514. $scrollback = '';
  515. $last_attr = $this->base_attr_cell;
  516. for ($i = 0; $i < count($this->history); $i++) {
  517. for ($j = 0; $j <= $this->max_x + 1; $j++) {
  518. $cur_attr = $this->history_attrs[$i][$j];
  519. $scrollback.= $this->_processCoordinate($last_attr, $cur_attr, isset($this->history[$i][$j]) ? $this->history[$i][$j] : '');
  520. $last_attr = $this->history_attrs[$i][$j];
  521. }
  522. $scrollback.= "\r\n";
  523. }
  524. $base_attr_cell = $this->base_attr_cell;
  525. $this->base_attr_cell = $last_attr;
  526. $scrollback.= $this->_getScreen();
  527. $this->base_attr_cell = $base_attr_cell;
  528. return '<pre width="' . ($this->max_x + 1) . '" style="color: white; background: black">' . $scrollback . '</span></pre>';
  529. }
  530. }