/framework/ActiveSync/lib/Horde/ActiveSync/Wbxml/Decoder.php

https://github.com/ewandor/horde · PHP · 575 lines · 359 code · 71 blank · 145 comment · 53 complexity · 53b96608a40df39072812695bb5ba0dc MD5 · raw file

  1. <?php
  2. /**
  3. * ActiveSync specific WBXML decoder.
  4. *
  5. * @author Michael J. Rubinsky <mrubinsk@horde.org>
  6. * @package ActiveSync
  7. */
  8. /**
  9. * File : wbxml.php
  10. * Project : Z-Push
  11. * Descr : WBXML mapping file
  12. *
  13. * Created : 01.10.2007
  14. *
  15. * � Zarafa Deutschland GmbH, www.zarafaserver.de
  16. * This file is distributed under GPL-2.0.
  17. * Consult COPYING file for details
  18. */
  19. class Horde_ActiveSync_Wbxml_Decoder extends Horde_ActiveSync_Wbxml
  20. {
  21. // These seem to only be used in the Const'r, and I can't find any
  22. // client code that access these properties...
  23. public $version;
  24. public $publicid;
  25. public $publicstringid;
  26. public $charsetid;
  27. public $stringtable;
  28. private $_attrcp = 0;
  29. private $_ungetbuffer;
  30. /**
  31. * Start reading the wbxml stream, pulling off the initial header and
  32. * populate the properties.
  33. *
  34. * @return void
  35. */
  36. public function readWbxmlHeader()
  37. {
  38. $this->version = $this->_getByte();
  39. $this->publicid = $this->_getMBUInt();
  40. if ($this->publicid == 0) {
  41. $this->publicstringid = $this->_getMBUInt();
  42. }
  43. $this->charsetid = $this->_getMBUInt();
  44. $this->stringtable = $this->_getStringTable();
  45. }
  46. /**
  47. * Set the logger instance
  48. *
  49. * @param Horde_Log_Logger $logger The logger.
  50. */
  51. public function setLogger(Horde_Log_Logger $logger)
  52. {
  53. $this->_logger = $logger;
  54. }
  55. /**
  56. * Returns either start, content or end, and auto-concatenates successive
  57. * content
  58. */
  59. public function getElement()
  60. {
  61. $element = $this->getToken();
  62. switch ($element[Horde_ActiveSync_Wbxml::EN_TYPE]) {
  63. case Horde_ActiveSync_Wbxml::EN_TYPE_STARTTAG:
  64. return $element;
  65. case Horde_ActiveSync_Wbxml::EN_TYPE_ENDTAG:
  66. return $element;
  67. case Horde_ActiveSync_Wbxml::EN_TYPE_CONTENT:
  68. while (1) {
  69. $next = $this->getToken();
  70. if ($next == false) {
  71. return false;
  72. } elseif ($next[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_CONTENT) {
  73. $element[Horde_ActiveSync_Wbxml::EN_CONTENT] .= $next[Horde_ActiveSync_Wbxml::EN_CONTENT];
  74. } else {
  75. $this->_ungetElement($next);
  76. break;
  77. }
  78. }
  79. return $element;
  80. }
  81. return false;
  82. }
  83. /**
  84. *
  85. * @return array The next element in the stream.
  86. */
  87. public function peek()
  88. {
  89. $element = $this->getElement();
  90. $this->_ungetElement($element);
  91. return $element;
  92. }
  93. /**
  94. * Get the next tag, which is assumed to be a start tag.
  95. *
  96. * @param string $tag The element that this should be a start tag for.
  97. *
  98. * @return mixed The start tag array | false on failure.
  99. */
  100. public function getElementStartTag($tag)
  101. {
  102. $element = $this->getToken();
  103. if ($element[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_STARTTAG &&
  104. $element[Horde_ActiveSync_Wbxml::EN_TAG] == $tag) {
  105. return $element;
  106. } else {
  107. $this->_ungetElement($element);
  108. }
  109. return false;
  110. }
  111. /**
  112. * Get the next tag, which is assumed to be an end tag.
  113. *
  114. * @return mixed The element array | false on failure.
  115. */
  116. public function getElementEndTag()
  117. {
  118. $element = $this->getToken();
  119. if ($element[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_ENDTAG) {
  120. return $element;
  121. } else {
  122. $this->_logger->err('Unmatched end tag:');
  123. $this->_logger->err(print_r($element, true));
  124. $this->_ungetElement($element);
  125. }
  126. return false;
  127. }
  128. /**
  129. * Get the element contents
  130. *
  131. * @return mixed The content of the current element | false on failure.
  132. */
  133. public function getElementContent()
  134. {
  135. $element = $this->getToken();
  136. if ($element[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_CONTENT) {
  137. return $element[Horde_ActiveSync_Wbxml::EN_CONTENT];
  138. } else {
  139. $this->_logger->err('Unmatched content:');
  140. $this->_logger->err(print_r($element, true));
  141. $this->_ungetElement($element);
  142. }
  143. return false;
  144. }
  145. /**
  146. * Get the next [start | content | end] tag.
  147. *
  148. * @return array The next, complete, token array.
  149. */
  150. public function getToken()
  151. {
  152. // See if there's something in the ungetBuffer
  153. if ($this->_ungetbuffer) {
  154. $element = $this->_ungetbuffer;
  155. $this->_ungetbuffer = false;
  156. return $element;
  157. }
  158. $el = $this->_getToken();
  159. $this->_logToken($el);
  160. return $el;
  161. }
  162. /**
  163. * Log the token.
  164. *
  165. * @param array The element array.
  166. *
  167. * @return void
  168. */
  169. private function _logToken($el)
  170. {
  171. $spaces = str_repeat(' ', count($this->_logStack));
  172. switch ($el[Horde_ActiveSync_Wbxml::EN_TYPE]) {
  173. case Horde_ActiveSync_Wbxml::EN_TYPE_STARTTAG:
  174. if ($el[Horde_ActiveSync_Wbxml::EN_FLAGS] & Horde_ActiveSync_Wbxml::EN_FLAGS_CONTENT) {
  175. $this->_logger->debug('I ' . $spaces . ' <' . $el[Horde_ActiveSync_Wbxml::EN_TAG] . '>');
  176. array_push($this->_logStack, $el[Horde_ActiveSync_Wbxml::EN_TAG]);
  177. } else {
  178. $this->_logger->debug('I ' . $spaces . ' <' . $el[Horde_ActiveSync_Wbxml::EN_TAG] . '/>');
  179. }
  180. break;
  181. case Horde_ActiveSync_Wbxml::EN_TYPE_ENDTAG:
  182. $tag = array_pop($this->_logStack);
  183. $this->_logger->debug('I ' . $spaces . '</' . $tag . '>');
  184. break;
  185. case Horde_ActiveSync_Wbxml::EN_TYPE_CONTENT:
  186. $this->_logger->debug('I ' . $spaces . ' ' . $el[Horde_ActiveSync_Wbxml::EN_CONTENT]);
  187. break;
  188. }
  189. }
  190. /**
  191. * Get the next start tag, content or end tag
  192. *
  193. * @return array The element array.
  194. */
  195. private function _getToken() {
  196. // Get the data from the input stream
  197. $element = array();
  198. while (1) {
  199. $byte = $this->_getByte();
  200. if (!isset($byte)) {
  201. break;
  202. }
  203. switch ($byte) {
  204. case Horde_ActiveSync_Wbxml::SWITCH_PAGE:
  205. $this->_tagcp = $this->_getByte();
  206. continue;
  207. case Horde_ActiveSync_Wbxml::END:
  208. $element[Horde_ActiveSync_Wbxml::EN_TYPE] = Horde_ActiveSync_Wbxml::EN_TYPE_ENDTAG;
  209. return $element;
  210. case Horde_ActiveSync_Wbxml::ENTITY:
  211. $entity = $this->_getMBUInt();
  212. $element[Horde_ActiveSync_Wbxml::EN_TYPE] = Horde_ActiveSync_Wbxml::EN_TYPE_CONTENT;
  213. $element[Horde_ActiveSync_Wbxml::EN_CONTENT] = $this->entityToCharset($entity);
  214. return $element;
  215. case Horde_ActiveSync_Wbxml::STR_I:
  216. $element[Horde_ActiveSync_Wbxml::EN_TYPE] = Horde_ActiveSync_Wbxml::EN_TYPE_CONTENT;
  217. $element[Horde_ActiveSync_Wbxml::EN_CONTENT] = $this->_getTermStr();
  218. return $element;
  219. case Horde_ActiveSync_Wbxml::LITERAL:
  220. $element[Horde_ActiveSync_Wbxml::EN_TYPE] = Horde_ActiveSync_Wbxml::EN_TYPE_STARTTAG;
  221. $element[Horde_ActiveSync_Wbxml::EN_TAG] = $this->_getStringTableEntry($this->_getMBUInt());
  222. $element[Horde_ActiveSync_Wbxml::EN_FLAGS] = 0;
  223. return $element;
  224. case Horde_ActiveSync_Wbxml::EXT_I_0:
  225. case Horde_ActiveSync_Wbxml::EXT_I_1:
  226. case Horde_ActiveSync_Wbxml::EXT_I_2:
  227. $this->_getTermStr();
  228. // Ignore extensions
  229. continue;
  230. case Horde_ActiveSync_Wbxml::PI:
  231. // Ignore PI
  232. $this->_getAttributes();
  233. continue;
  234. case Horde_ActiveSync_Wbxml::LITERAL_C:
  235. $element[Horde_ActiveSync_Wbxml::EN_TYPE] = Horde_ActiveSync_Wbxml::EN_TYPE_STARTTAG;
  236. $element[Horde_ActiveSync_Wbxml::EN_TAG] = $this->_getStringTableEntry($this->_getMBUInt());
  237. $element[Horde_ActiveSync_Wbxml::EN_FLAGS] = Horde_ActiveSync_Wbxml::EN_FLAGS_CONTENT;
  238. return $element;
  239. case Horde_ActiveSync_Wbxml::EXT_T_0:
  240. case Horde_ActiveSync_Wbxml::EXT_T_1:
  241. case Horde_ActiveSync_Wbxml::EXT_T_2:
  242. $this->_getMBUInt();
  243. // Ingore extensions;
  244. continue;
  245. case Horde_ActiveSync_Wbxml::STR_T:
  246. $element[Horde_ActiveSync_Wbxml::EN_TYPE] = Horde_ActiveSync_Wbxml::EN_TYPE_CONTENT;
  247. $element[Horde_ActiveSync_Wbxml::EN_CONTENT] = $this->_getStringTableEntry($this->_getMBUInt());
  248. return $element;
  249. case Horde_ActiveSync_Wbxml::LITERAL_A:
  250. $element[Horde_ActiveSync_Wbxml::EN_TYPE] = Horde_ActiveSync_Wbxml::EN_TYPE_STARTTAG;
  251. $element[Horde_ActiveSync_Wbxml::EN_TAG] = $this->_getStringTableEntry($this->_getMBUInt());
  252. $element[Horde_ActiveSync_Wbxml::EN_ATTRIBUTES] = $this->_getAttributes();
  253. $element[Horde_ActiveSync_Wbxml::EN_FLAGS] = Horde_ActiveSync_Wbxml::EN_FLAGS_ATTRIBUTES;
  254. return $element;
  255. case Horde_ActiveSync_Wbxml::EXT_0:
  256. case Horde_ActiveSync_Wbxml::EXT_1:
  257. case Horde_ActiveSync_Wbxml::EXT_2:
  258. continue;
  259. case Horde_ActiveSync_Wbxml::OPAQUE:
  260. $length = $this->_getMBUInt();
  261. $element[Horde_ActiveSync_Wbxml::EN_TYPE] = Horde_ActiveSync_Wbxml::EN_TYPE_CONTENT;
  262. $element[Horde_ActiveSync_Wbxml::EN_CONTENT] = $this->_getOpaque($length);
  263. return $element;
  264. case Horde_ActiveSync_Wbxml::LITERAL_AC:
  265. $element[Horde_ActiveSync_Wbxml::EN_TYPE] = Horde_ActiveSync_Wbxml::EN_TYPE_STARTTAG;
  266. $element[Horde_ActiveSync_Wbxml::EN_TAG] = $this->_getStringTableEntry($this->_getMBUInt());
  267. $element[Horde_ActiveSync_Wbxml::EN_ATTRIBUTES] = $this->_getAttributes();
  268. $element[Horde_ActiveSync_Wbxml::EN_FLAGS] = Horde_ActiveSync_Wbxml::EN_FLAGS_ATTRIBUTES | Horde_ActiveSync_Wbxml::EN_FLAGS_CONTENT;
  269. return $element;
  270. default:
  271. $element[Horde_ActiveSync_Wbxml::EN_TYPE] = Horde_ActiveSync_Wbxml::EN_TYPE_STARTTAG;
  272. $element[Horde_ActiveSync_Wbxml::EN_TAG] = $this->_getMapping($this->_tagcp, $byte & 0x3f);
  273. $element[Horde_ActiveSync_Wbxml::EN_FLAGS] = ($byte & 0x80 ? Horde_ActiveSync_Wbxml::EN_FLAGS_ATTRIBUTES : 0) | ($byte & 0x40 ? Horde_ActiveSync_Wbxml::EN_FLAGS_CONTENT : 0);
  274. if ($byte & 0x80) {
  275. $element[Horde_ActiveSync_Wbxml::EN_ATTRIBUTES] = $this->_getAttributes();
  276. }
  277. return $element;
  278. }
  279. }
  280. }
  281. /**
  282. * Unget the specified element from the stream. Places the element into
  283. * the unget buffer.
  284. *
  285. * @param array $element The element array to unget.
  286. *
  287. * @return void
  288. */
  289. public function _ungetElement($element)
  290. {
  291. if ($this->_ungetbuffer) {
  292. $this->_logger->err('Double unget!');
  293. }
  294. $this->_ungetbuffer = $element;
  295. }
  296. /**
  297. * Get the element attributes
  298. *
  299. * @return mixed The value of the element's attributes.
  300. */
  301. private function _getAttributes()
  302. {
  303. $attributes = array();
  304. $attr = '';
  305. while (1) {
  306. $byte = $this->_getByte();
  307. if (count($byte) == 0) {
  308. break;
  309. }
  310. switch($byte) {
  311. case Horde_ActiveSync_Wbxml::SWITCH_PAGE:
  312. $this->_attrcp = $this->_getByte();
  313. break;
  314. case Horde_ActiveSync_Wbxml::END:
  315. if ($attr != '') {
  316. $attributes += $this->_splitAttribute($attr);
  317. }
  318. return $attributes;
  319. case Horde_ActiveSync_Wbxml::ENTITY:
  320. $entity = $this->_getMBUInt();
  321. $attr .= $this->entityToCharset($entity);
  322. return $element;
  323. case Horde_ActiveSync_Wbxml::STR_I:
  324. $attr .= $this->_getTermStr();
  325. return $element;
  326. case Horde_ActiveSync_Wbxml::LITERAL:
  327. if ($attr != '') {
  328. $attributes += $this->_splitAttribute($attr);
  329. }
  330. $attr = $this->_getStringTableEntry($this->_getMBUInt());
  331. return $element;
  332. case Horde_ActiveSync_Wbxml::EXT_I_0:
  333. case Horde_ActiveSync_Wbxml::EXT_I_1:
  334. case Horde_ActiveSync_Wbxml::EXT_I_2:
  335. $this->_getTermStr();
  336. continue;
  337. case Horde_ActiveSync_Wbxml::PI:
  338. case Horde_ActiveSync_Wbxml::LITERAL_C:
  339. // Invalid
  340. return false;
  341. case Horde_ActiveSync_Wbxml::EXT_T_0:
  342. case Horde_ActiveSync_Wbxml::EXT_T_1:
  343. case Horde_ActiveSync_Wbxml::EXT_T_2:
  344. $this->_getMBUInt();
  345. continue;
  346. case Horde_ActiveSync_Wbxml::STR_T:
  347. $attr .= $this->_getStringTableEntry($this->_getMBUInt());
  348. return $element;
  349. case Horde_ActiveSync_Wbxml::LITERAL_A:
  350. return false;
  351. case Horde_ActiveSync_Wbxml::EXT_0:
  352. case Horde_ActiveSync_Wbxml::EXT_1:
  353. case Horde_ActiveSync_Wbxml::EXT_2:
  354. continue;
  355. case Horde_ActiveSync_Wbxml::OPAQUE:
  356. $length = $this->_getMBUInt();
  357. $attr .= $this->_getOpaque($length);
  358. return $element;
  359. case Horde_ActiveSync_Wbxml::LITERAL_AC:
  360. return false;
  361. default:
  362. if ($byte < 128) {
  363. if ($attr != '') {
  364. $attributes += $this->_splitAttribute($attr);
  365. $attr = '';
  366. }
  367. }
  368. $attr .= $this->_getMapping($this->_attrcp, $byte);
  369. break;
  370. }
  371. }
  372. }
  373. /**
  374. * Parses an attribute string
  375. *
  376. * @param string $attr The raw attribute value.
  377. *
  378. * @return array The attribute hash
  379. */
  380. private function _splitAttribute($attr)
  381. {
  382. $attributes = array();
  383. $pos = strpos($attr,chr(61)); // equals sign
  384. if ($pos) {
  385. $attributes[substr($attr, 0, $pos)] = substr($attr, $pos+1);
  386. } else {
  387. $attributes[$attr] = null;
  388. }
  389. return $attributes;
  390. }
  391. /**
  392. * Get a null terminated string from the stream.
  393. *
  394. * @return string The string
  395. */
  396. private function _getTermStr()
  397. {
  398. $str = '';
  399. while(1) {
  400. $in = $this->_getByte();
  401. if ($in == 0) {
  402. break;
  403. } else {
  404. $str .= chr($in);
  405. }
  406. }
  407. return $str;
  408. }
  409. /**
  410. * Get an opaque value from the stream of the specified length.
  411. *
  412. * @param integer $len The length of the data to fetch.
  413. *
  414. * @return string A string of bytes representing the opaque value.
  415. */
  416. private function _getOpaque($len)
  417. {
  418. return fread($this->_stream, $len);
  419. }
  420. /**
  421. * Fetch a single byte from the stream.
  422. *
  423. * @return string The single byte.
  424. */
  425. private function _getByte()
  426. {
  427. $ch = fread($this->_stream, 1);
  428. if (strlen($ch) > 0) {
  429. $ch = ord($ch);
  430. //$this->_logger->debug('_getByte: ' . $ch);
  431. return $ch;
  432. } else {
  433. return;
  434. }
  435. }
  436. /**
  437. * Get an MBU integer
  438. *
  439. * @return integer
  440. */
  441. private function _getMBUInt()
  442. {
  443. $uint = 0;
  444. while (1) {
  445. $byte = $this->_getByte();
  446. $uint |= $byte & 0x7f;
  447. if ($byte & 0x80) {
  448. $uint = $uint << 7;
  449. } else {
  450. break;
  451. }
  452. }
  453. return $uint;
  454. }
  455. /**
  456. * Fetch the string table. Don't think we use the results anywhere though.
  457. *
  458. * @return string The string table.
  459. */
  460. private function _getStringTable()
  461. {
  462. $stringtable = '';
  463. $length = $this->_getMBUInt();
  464. if ($length > 0) {
  465. $stringtable = fread($this->_stream, $length);
  466. }
  467. return $stringtable;
  468. }
  469. /**
  470. * Really don't know for sure what this method is supposed to do, it is
  471. * called from numerous places in this class, but the original zpush code
  472. * did not contain this method...so, either it's completely broken, or
  473. * normal use-cases do not reach the calling code. Either way, it needs to
  474. * eventually be fixed.
  475. *
  476. * @param integer $id The entry to return??
  477. *
  478. * @return string
  479. */
  480. private function _getStringTableEntry($id)
  481. {
  482. throw new Horde_ActiveSync_Exception('Not implemented');
  483. }
  484. /**
  485. * Get a dtd mapping
  486. *
  487. * @param integer $cp The codepage to use.
  488. * @param integer $id The property.
  489. *
  490. * @return mixed The mapped value.
  491. */
  492. private function _getMapping($cp, $id)
  493. {
  494. if (!isset($this->_dtd['codes'][$cp]) || !isset($this->_dtd['codes'][$cp][$id])) {
  495. return false;
  496. } else {
  497. if (isset($this->_dtd['namespaces'][$cp])) {
  498. return $this->_dtd['namespaces'][$cp] . ':' . $this->_dtd['codes'][$cp][$id];
  499. } else {
  500. return $this->_dtd['codes'][$cp][$id];
  501. }
  502. }
  503. }
  504. }