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