選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

rcube_tnef_decoder.php 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. <?php
  2. /**
  3. +-----------------------------------------------------------------------+
  4. | This file is part of the Roundcube Webmail client |
  5. | Copyright (C) 2008-2017, 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. | Author: Aleksander Machniak <alec@alec.pl> |
  18. +-----------------------------------------------------------------------+
  19. */
  20. /**
  21. * MS-TNEF format decoder based on code by:
  22. * Graham Norbury <gnorbury@bondcar.com>
  23. * Original design by:
  24. * Thomas Boll <tb@boll.ch>, Mark Simpson <damned@world.std.com>
  25. *
  26. * @package Framework
  27. * @subpackage Storage
  28. */
  29. class rcube_tnef_decoder
  30. {
  31. const SIGNATURE = 0x223e9f78;
  32. const LVL_MESSAGE = 0x01;
  33. const LVL_ATTACHMENT = 0x02;
  34. const ASUBJECT = 0x88004;
  35. const AMCLASS = 0x78008;
  36. const ATTACHDATA = 0x6800f;
  37. const AFILENAME = 0x18010;
  38. const ARENDDATA = 0x69002;
  39. const AMAPIATTRS = 0x69005;
  40. const AOEMCODEPAGE = 0x69007;
  41. const AVERSION = 0x89006;
  42. const MAPI_NULL = 0x0001;
  43. const MAPI_SHORT = 0x0002;
  44. const MAPI_INT = 0x0003;
  45. const MAPI_FLOAT = 0x0004;
  46. const MAPI_DOUBLE = 0x0005;
  47. const MAPI_CURRENCY = 0x0006;
  48. const MAPI_APPTIME = 0x0007;
  49. const MAPI_ERROR = 0x000a;
  50. const MAPI_BOOLEAN = 0x000b;
  51. const MAPI_OBJECT = 0x000d;
  52. const MAPI_INT8BYTE = 0x0014;
  53. const MAPI_STRING = 0x001e;
  54. const MAPI_UNICODE_STRING = 0x001f;
  55. const MAPI_SYSTIME = 0x0040;
  56. const MAPI_CLSID = 0x0048;
  57. const MAPI_BINARY = 0x0102;
  58. const MAPI_DISPLAY_NAME = 0x3001;
  59. const MAPI_ADDRTYPE = 0x3002;
  60. const MAPI_EMAIL_ADDRESS = 0x3003;
  61. const MAPI_COMMENT = 0x3004;
  62. const MAPI_DEPTH = 0x3005;
  63. const MAPI_PROVIDER_DISPLAY = 0x3006;
  64. const MAPI_CREATION_TIME = 0x3007;
  65. const MAPI_LAST_MODIFICATION_TIME = 0x3008;
  66. const MAPI_RESOURCE_FLAGS = 0x3009;
  67. const MAPI_PROVIDER_DLL_NAME = 0x300A;
  68. const MAPI_SEARCH_KEY = 0x300B;
  69. const MAPI_ATTACHMENT_X400_PARAMETERS = 0x3700;
  70. const MAPI_ATTACH_DATA_OBJ = 0x3701;
  71. const MAPI_ATTACH_ENCODING = 0x3702;
  72. const MAPI_ATTACH_EXTENSION = 0x3703;
  73. const MAPI_ATTACH_FILENAME = 0x3704;
  74. const MAPI_ATTACH_METHOD = 0x3705;
  75. const MAPI_ATTACH_LONG_FILENAME = 0x3707;
  76. const MAPI_ATTACH_PATHNAME = 0x3708;
  77. const MAPI_ATTACH_RENDERING = 0x3709;
  78. const MAPI_ATTACH_TAG = 0x370A;
  79. const MAPI_RENDERING_POSITION = 0x370B;
  80. const MAPI_ATTACH_TRANSPORT_NAME = 0x370C;
  81. const MAPI_ATTACH_LONG_PATHNAME = 0x370D;
  82. const MAPI_ATTACH_MIME_TAG = 0x370E;
  83. const MAPI_ATTACH_ADDITIONAL_INFO = 0x370F;
  84. const MAPI_ATTACH_MIME_SEQUENCE = 0x3710;
  85. const MAPI_ATTACH_CONTENT_ID = 0x3712;
  86. const MAPI_ATTACH_CONTENT_LOCATION = 0x3713;
  87. const MAPI_ATTACH_FLAGS = 0x3714;
  88. const MAPI_NAMED_TYPE_ID = 0x0000;
  89. const MAPI_NAMED_TYPE_STRING = 0x0001;
  90. const MAPI_MV_FLAG = 0x1000;
  91. /**
  92. * Decompress the data.
  93. *
  94. * @param string $data The data to decompress.
  95. * @param array $params An array of arguments needed to decompress the
  96. * data.
  97. *
  98. * @return mixed The decompressed data.
  99. */
  100. public function decompress($data, $params = array())
  101. {
  102. $out = array();
  103. if ($this->_geti($data, 32) == self::SIGNATURE) {
  104. $this->_geti($data, 16);
  105. while (strlen($data) > 0) {
  106. switch ($this->_geti($data, 8)) {
  107. case self::LVL_MESSAGE:
  108. $this->_decodeMessage($data);
  109. break;
  110. case self::LVL_ATTACHMENT:
  111. $this->_decodeAttachment($data, $out);
  112. break;
  113. }
  114. }
  115. }
  116. return array_reverse($out);
  117. }
  118. /**
  119. * TODO
  120. *
  121. * @param string &$data The data string.
  122. * @param integer $bits How many bits to retrieve.
  123. *
  124. * @return TODO
  125. */
  126. protected function _getx(&$data, $bits)
  127. {
  128. $value = null;
  129. if (strlen($data) >= $bits) {
  130. $value = substr($data, 0, $bits);
  131. $data = substr_replace($data, '', 0, $bits);
  132. }
  133. return $value;
  134. }
  135. /**
  136. * TODO
  137. *
  138. * @param string &$data The data string.
  139. * @param integer $bits How many bits to retrieve.
  140. *
  141. * @return TODO
  142. */
  143. protected function _geti(&$data, $bits)
  144. {
  145. $bytes = $bits / 8;
  146. $value = null;
  147. if (strlen($data) >= $bytes) {
  148. $value = ord($data[0]);
  149. if ($bytes >= 2) {
  150. $value += (ord($data[1]) << 8);
  151. }
  152. if ($bytes >= 4) {
  153. $value += (ord($data[2]) << 16) + (ord($data[3]) << 24);
  154. }
  155. $data = substr_replace($data, '', 0, $bytes);
  156. }
  157. return $value;
  158. }
  159. /**
  160. * TODO
  161. *
  162. * @param string &$data The data string.
  163. * @param string $attribute TODO
  164. */
  165. protected function _decodeAttribute(&$data, $attribute)
  166. {
  167. /* Data. */
  168. $value = $this->_getx($data, $this->_geti($data, 32));
  169. /* Checksum. */
  170. $this->_geti($data, 16);
  171. return $value;
  172. }
  173. /**
  174. * TODO
  175. *
  176. * @param string $data The data string.
  177. * @param array &$attachment_data TODO
  178. */
  179. protected function _extractMapiAttributes($data, &$attachment_data)
  180. {
  181. /* Number of attributes. */
  182. $number = $this->_geti($data, 32);
  183. while ((strlen($data) > 0) && $number--) {
  184. $have_mval = false;
  185. $num_mval = 1;
  186. $named_id = $value = null;
  187. $attr_type = $this->_geti($data, 16);
  188. $attr_name = $this->_geti($data, 16);
  189. if (($attr_type & self::MAPI_MV_FLAG) != 0) {
  190. $have_mval = true;
  191. $attr_type = $attr_type & ~self::MAPI_MV_FLAG;
  192. }
  193. if (($attr_name >= 0x8000) && ($attr_name < 0xFFFE)) {
  194. $this->_getx($data, 16);
  195. $named_type = $this->_geti($data, 32);
  196. switch ($named_type) {
  197. case self::MAPI_NAMED_TYPE_ID:
  198. $named_id = $this->_geti($data, 32);
  199. $attr_name = $named_id;
  200. break;
  201. case self::MAPI_NAMED_TYPE_STRING:
  202. $attr_name = 0x9999;
  203. $idlen = $this->_geti($data, 32);
  204. $datalen = $idlen + ((4 - ($idlen % 4)) % 4);
  205. $named_id = substr($this->_getx($data, $datalen), 0, $idlen);
  206. break;
  207. }
  208. }
  209. if ($have_mval) {
  210. $num_mval = $this->_geti($data, 32);
  211. }
  212. switch ($attr_type) {
  213. case self::MAPI_SHORT:
  214. $value = $this->_geti($data, 16);
  215. break;
  216. case self::MAPI_INT:
  217. case self::MAPI_BOOLEAN:
  218. for ($i = 0; $i < $num_mval; $i++) {
  219. $value = $this->_geti($data, 32);
  220. }
  221. break;
  222. case self::MAPI_FLOAT:
  223. case self::MAPI_ERROR:
  224. $value = $this->_getx($data, 4);
  225. break;
  226. case self::MAPI_DOUBLE:
  227. case self::MAPI_APPTIME:
  228. case self::MAPI_CURRENCY:
  229. case self::MAPI_INT8BYTE:
  230. case self::MAPI_SYSTIME:
  231. $value = $this->_getx($data, 8);
  232. break;
  233. case self::MAPI_STRING:
  234. case self::MAPI_UNICODE_STRING:
  235. case self::MAPI_BINARY:
  236. case self::MAPI_OBJECT:
  237. $num_vals = $have_mval ? $num_mval : $this->_geti($data, 32);
  238. for ($i = 0; $i < $num_vals; $i++) {
  239. $length = $this->_geti($data, 32);
  240. /* Pad to next 4 byte boundary. */
  241. $datalen = $length + ((4 - ($length % 4)) % 4);
  242. if ($attr_type == self::MAPI_STRING) {
  243. --$length;
  244. }
  245. /* Read and truncate to length. */
  246. $value = substr($this->_getx($data, $datalen), 0, $length);
  247. }
  248. break;
  249. }
  250. /* Store any interesting attributes. */
  251. switch ($attr_name) {
  252. case self::MAPI_ATTACH_LONG_FILENAME:
  253. $value = $this->convertString($value);
  254. /* Used in preference to AFILENAME value. */
  255. $attachment_data[0]['name'] = preg_replace('/.*[\/](.*)$/', '\1', $value);
  256. break;
  257. case self::MAPI_ATTACH_MIME_TAG:
  258. $value = $this->convertString($value);
  259. /* Is this ever set, and what is format? */
  260. $attachment_data[0]['type'] = preg_replace('/^(.*)\/.*/', '\1', $value);
  261. $attachment_data[0]['subtype'] = preg_replace('/.*\/(.*)$/', '\1', $value);
  262. break;
  263. }
  264. }
  265. }
  266. /**
  267. * TODO
  268. *
  269. * @param string &$data The data string.
  270. */
  271. protected function _decodeMessage(&$data)
  272. {
  273. $attribute = $this->_geti($data, 32);
  274. $value = $this->_decodeAttribute($data, $attribute);
  275. switch ($attribute) {
  276. case self::AOEMCODEPAGE:
  277. // Find codepage of the message
  278. $value = unpack('V', $value);
  279. $this->codepage = $value[1];
  280. break;
  281. default:
  282. }
  283. }
  284. /**
  285. * TODO
  286. *
  287. * @param string &$data The data string.
  288. * @param array &$attachment_data TODO
  289. */
  290. protected function _decodeAttachment(&$data, &$attachment_data)
  291. {
  292. $attribute = $this->_geti($data, 32);
  293. switch ($attribute) {
  294. case self::ARENDDATA:
  295. /* Marks start of new attachment. */
  296. $this->_getx($data, $this->_geti($data, 32));
  297. /* Checksum */
  298. $this->_geti($data, 16);
  299. /* Add a new default data block to hold details of this
  300. attachment. Reverse order is easier to handle later! */
  301. array_unshift($attachment_data, array('type' => 'application',
  302. 'subtype' => 'octet-stream',
  303. 'name' => 'unknown',
  304. 'stream' => ''));
  305. break;
  306. case self::AFILENAME:
  307. $value = $this->_getx($data, $this->_geti($data, 32));
  308. $value = $this->convertString($value, true);
  309. /* Strip path. */
  310. $attachment_data[0]['name'] = preg_replace('/.*[\/](.*)$/', '\1', $value);
  311. /* Checksum */
  312. $this->_geti($data, 16);
  313. break;
  314. case self::ATTACHDATA:
  315. /* The attachment itself. */
  316. $length = $this->_geti($data, 32);
  317. $attachment_data[0]['size'] = $length;
  318. $attachment_data[0]['stream'] = $this->_getx($data, $length);
  319. /* Checksum */
  320. $this->_geti($data, 16);
  321. break;
  322. case self::AMAPIATTRS:
  323. $value = $this->_getx($data, $this->_geti($data, 32));
  324. /* Checksum */
  325. $this->_geti($data, 16);
  326. $this->_extractMapiAttributes($value, $attachment_data);
  327. break;
  328. default:
  329. $this->_decodeAttribute($data, $attribute);
  330. }
  331. }
  332. /**
  333. * Convert string value to system charset according to defined codepage
  334. */
  335. protected function convertString($str, $use_codepage = false)
  336. {
  337. if ($convert && $this->codepage
  338. && ($charset = rcube_charset::$windows_codepages[$this->codepage])
  339. ) {
  340. $str = rcube_charset::convert($str, $charset);
  341. }
  342. else if (strpos($str, "\0") !== false) {
  343. $str = rcube_charset::convert($str, 'UTF-16LE');
  344. }
  345. $str = rtrim($str, "\0");
  346. return $str;
  347. }
  348. }