You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

rcube_tnef_decoder.php 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. <?php
  2. /**
  3. +-----------------------------------------------------------------------+
  4. | This file is part of the Roundcube Webmail client |
  5. | Copyright (C) 2008-2014, The Roundcube Dev Team |
  6. | Copyright (C) 2002-2010, The Horde Project (http://www.horde.org/) |
  7. | |
  8. | Licensed under the GNU General Public License version 3 or |
  9. | any later version with exceptions for skins & plugins. |
  10. | See the README file for a full license statement. |
  11. | |
  12. | PURPOSE: |
  13. | MS-TNEF format decoder |
  14. +-----------------------------------------------------------------------+
  15. | Author: Jan Schneider <jan@horde.org> |
  16. | Author: Michael Slusarz <slusarz@horde.org> |
  17. +-----------------------------------------------------------------------+
  18. */
  19. /**
  20. * MS-TNEF format decoder based on code by:
  21. * Graham Norbury <gnorbury@bondcar.com>
  22. * Original design by:
  23. * Thomas Boll <tb@boll.ch>, Mark Simpson <damned@world.std.com>
  24. *
  25. * @package Framework
  26. * @subpackage Storage
  27. */
  28. class rcube_tnef_decoder
  29. {
  30. const SIGNATURE = 0x223e9f78;
  31. const LVL_MESSAGE = 0x01;
  32. const LVL_ATTACHMENT = 0x02;
  33. const ASUBJECT = 0x88004;
  34. const AMCLASS = 0x78008;
  35. const ATTACHDATA = 0x6800f;
  36. const AFILENAME = 0x18010;
  37. const ARENDDATA = 0x69002;
  38. const AMAPIATTRS = 0x69005;
  39. const AVERSION = 0x89006;
  40. const MAPI_NULL = 0x0001;
  41. const MAPI_SHORT = 0x0002;
  42. const MAPI_INT = 0x0003;
  43. const MAPI_FLOAT = 0x0004;
  44. const MAPI_DOUBLE = 0x0005;
  45. const MAPI_CURRENCY = 0x0006;
  46. const MAPI_APPTIME = 0x0007;
  47. const MAPI_ERROR = 0x000a;
  48. const MAPI_BOOLEAN = 0x000b;
  49. const MAPI_OBJECT = 0x000d;
  50. const MAPI_INT8BYTE = 0x0014;
  51. const MAPI_STRING = 0x001e;
  52. const MAPI_UNICODE_STRING = 0x001f;
  53. const MAPI_SYSTIME = 0x0040;
  54. const MAPI_CLSID = 0x0048;
  55. const MAPI_BINARY = 0x0102;
  56. const MAPI_ATTACH_LONG_FILENAME = 0x3707;
  57. const MAPI_ATTACH_MIME_TAG = 0x370E;
  58. const MAPI_NAMED_TYPE_ID = 0x0000;
  59. const MAPI_NAMED_TYPE_STRING = 0x0001;
  60. const MAPI_MV_FLAG = 0x1000;
  61. /**
  62. * Decompress the data.
  63. *
  64. * @param string $data The data to decompress.
  65. * @param array $params An array of arguments needed to decompress the
  66. * data.
  67. *
  68. * @return mixed The decompressed data.
  69. */
  70. public function decompress($data, $params = array())
  71. {
  72. $out = array();
  73. if ($this->_geti($data, 32) == self::SIGNATURE) {
  74. $this->_geti($data, 16);
  75. while (strlen($data) > 0) {
  76. switch ($this->_geti($data, 8)) {
  77. case self::LVL_MESSAGE:
  78. $this->_decodeMessage($data);
  79. break;
  80. case self::LVL_ATTACHMENT:
  81. $this->_decodeAttachment($data, $out);
  82. break;
  83. }
  84. }
  85. }
  86. return array_reverse($out);
  87. }
  88. /**
  89. * TODO
  90. *
  91. * @param string &$data The data string.
  92. * @param integer $bits How many bits to retrieve.
  93. *
  94. * @return TODO
  95. */
  96. protected function _getx(&$data, $bits)
  97. {
  98. $value = null;
  99. if (strlen($data) >= $bits) {
  100. $value = substr($data, 0, $bits);
  101. $data = substr_replace($data, '', 0, $bits);
  102. }
  103. return $value;
  104. }
  105. /**
  106. * TODO
  107. *
  108. * @param string &$data The data string.
  109. * @param integer $bits How many bits to retrieve.
  110. *
  111. * @return TODO
  112. */
  113. protected function _geti(&$data, $bits)
  114. {
  115. $bytes = $bits / 8;
  116. $value = null;
  117. if (strlen($data) >= $bytes) {
  118. $value = ord($data[0]);
  119. if ($bytes >= 2) {
  120. $value += (ord($data[1]) << 8);
  121. }
  122. if ($bytes >= 4) {
  123. $value += (ord($data[2]) << 16) + (ord($data[3]) << 24);
  124. }
  125. $data = substr_replace($data, '', 0, $bytes);
  126. }
  127. return $value;
  128. }
  129. /**
  130. * TODO
  131. *
  132. * @param string &$data The data string.
  133. * @param string $attribute TODO
  134. */
  135. protected function _decodeAttribute(&$data, $attribute)
  136. {
  137. /* Data. */
  138. $this->_getx($data, $this->_geti($data, 32));
  139. /* Checksum. */
  140. $this->_geti($data, 16);
  141. }
  142. /**
  143. * TODO
  144. *
  145. * @param string $data The data string.
  146. * @param array &$attachment_data TODO
  147. */
  148. protected function _extractMapiAttributes($data, &$attachment_data)
  149. {
  150. /* Number of attributes. */
  151. $number = $this->_geti($data, 32);
  152. while ((strlen($data) > 0) && $number--) {
  153. $have_mval = false;
  154. $num_mval = 1;
  155. $named_id = $value = null;
  156. $attr_type = $this->_geti($data, 16);
  157. $attr_name = $this->_geti($data, 16);
  158. if (($attr_type & self::MAPI_MV_FLAG) != 0) {
  159. $have_mval = true;
  160. $attr_type = $attr_type & ~self::MAPI_MV_FLAG;
  161. }
  162. if (($attr_name >= 0x8000) && ($attr_name < 0xFFFE)) {
  163. $this->_getx($data, 16);
  164. $named_type = $this->_geti($data, 32);
  165. switch ($named_type) {
  166. case self::MAPI_NAMED_TYPE_ID:
  167. $named_id = $this->_geti($data, 32);
  168. $attr_name = $named_id;
  169. break;
  170. case self::MAPI_NAMED_TYPE_STRING:
  171. $attr_name = 0x9999;
  172. $idlen = $this->_geti($data, 32);
  173. $datalen = $idlen + ((4 - ($idlen % 4)) % 4);
  174. $named_id = substr($this->_getx($data, $datalen), 0, $idlen);
  175. break;
  176. }
  177. }
  178. if ($have_mval) {
  179. $num_mval = $this->_geti($data, 32);
  180. }
  181. switch ($attr_type) {
  182. case self::MAPI_SHORT:
  183. $value = $this->_geti($data, 16);
  184. break;
  185. case self::MAPI_INT:
  186. case self::MAPI_BOOLEAN:
  187. for ($i = 0; $i < $num_mval; $i++) {
  188. $value = $this->_geti($data, 32);
  189. }
  190. break;
  191. case self::MAPI_FLOAT:
  192. case self::MAPI_ERROR:
  193. $value = $this->_getx($data, 4);
  194. break;
  195. case self::MAPI_DOUBLE:
  196. case self::MAPI_APPTIME:
  197. case self::MAPI_CURRENCY:
  198. case self::MAPI_INT8BYTE:
  199. case self::MAPI_SYSTIME:
  200. $value = $this->_getx($data, 8);
  201. break;
  202. case self::MAPI_STRING:
  203. case self::MAPI_UNICODE_STRING:
  204. case self::MAPI_BINARY:
  205. case self::MAPI_OBJECT:
  206. $num_vals = $have_mval ? $num_mval : $this->_geti($data, 32);
  207. for ($i = 0; $i < $num_vals; $i++) {
  208. $length = $this->_geti($data, 32);
  209. /* Pad to next 4 byte boundary. */
  210. $datalen = $length + ((4 - ($length % 4)) % 4);
  211. if ($attr_type == self::MAPI_STRING) {
  212. --$length;
  213. }
  214. /* Read and truncate to length. */
  215. $value = substr($this->_getx($data, $datalen), 0, $length);
  216. }
  217. break;
  218. }
  219. /* Store any interesting attributes. */
  220. switch ($attr_name) {
  221. case self::MAPI_ATTACH_LONG_FILENAME:
  222. $value = str_replace("\0", '', $value);
  223. /* Used in preference to AFILENAME value. */
  224. $attachment_data[0]['name'] = preg_replace('/.*[\/](.*)$/', '\1', $value);
  225. break;
  226. case self::MAPI_ATTACH_MIME_TAG:
  227. $value = str_replace("\0", '', $value);
  228. /* Is this ever set, and what is format? */
  229. $attachment_data[0]['type'] = preg_replace('/^(.*)\/.*/', '\1', $value);
  230. $attachment_data[0]['subtype'] = preg_replace('/.*\/(.*)$/', '\1', $value);
  231. break;
  232. }
  233. }
  234. }
  235. /**
  236. * TODO
  237. *
  238. * @param string &$data The data string.
  239. */
  240. protected function _decodeMessage(&$data)
  241. {
  242. $this->_decodeAttribute($data, $this->_geti($data, 32));
  243. }
  244. /**
  245. * TODO
  246. *
  247. * @param string &$data The data string.
  248. * @param array &$attachment_data TODO
  249. */
  250. protected function _decodeAttachment(&$data, &$attachment_data)
  251. {
  252. $attribute = $this->_geti($data, 32);
  253. switch ($attribute) {
  254. case self::ARENDDATA:
  255. /* Marks start of new attachment. */
  256. $this->_getx($data, $this->_geti($data, 32));
  257. /* Checksum */
  258. $this->_geti($data, 16);
  259. /* Add a new default data block to hold details of this
  260. attachment. Reverse order is easier to handle later! */
  261. array_unshift($attachment_data, array('type' => 'application',
  262. 'subtype' => 'octet-stream',
  263. 'name' => 'unknown',
  264. 'stream' => ''));
  265. break;
  266. case self::AFILENAME:
  267. $value = $this->_getx($data, $this->_geti($data, 32));
  268. $value = str_replace("\0", '', $value);
  269. /* Strip path. */
  270. $attachment_data[0]['name'] = preg_replace('/.*[\/](.*)$/', '\1', $value);
  271. /* Checksum */
  272. $this->_geti($data, 16);
  273. break;
  274. case self::ATTACHDATA:
  275. /* The attachment itself. */
  276. $length = $this->_geti($data, 32);
  277. $attachment_data[0]['size'] = $length;
  278. $attachment_data[0]['stream'] = $this->_getx($data, $length);
  279. /* Checksum */
  280. $this->_geti($data, 16);
  281. break;
  282. case self::AMAPIATTRS:
  283. $length = $this->_geti($data, 32);
  284. $value = $this->_getx($data, $length);
  285. /* Checksum */
  286. $this->_geti($data, 16);
  287. $this->_extractMapiAttributes($value, $attachment_data);
  288. break;
  289. default:
  290. $this->_decodeAttribute($data, $attribute);
  291. }
  292. }
  293. }