1<?php
2/**
3 * The Serialize:: class provides various methods of encapsulating data.
4 *
5 * Copyright 2001-2016 Horde LLC (http://www.horde.org/)
6 *
7 * See the enclosed file COPYING for license information (LGPL). If you
8 * did not receive this file, see http://www.horde.org/licenses/lgpl21.
9 *
10 * @author   Stephane Huther <shuther1@free.fr>
11 * @author   Michael Slusarz <slusarz@horde.org>
12 * @package  Serialize
13 * @category Horde
14 */
15class Horde_Serialize
16{
17    /* Type constants */
18    const UNKNOWN = -1;
19    const NONE = 0;
20    const WDDX = 1;
21    const BZIP = 2;
22    const IMAP8 = 3;
23    const IMAPUTF7 = 4;
24    const IMAPUTF8 = 5;
25    const BASIC = 6;
26    const GZ_DEFLATE = 7;
27    const GZ_COMPRESS = 8;
28    const GZ_ENCODE = 9;
29    const BASE64 = 10;
30    const RAW = 12;
31    const URL = 13;
32    const UTF7 = 14;
33    const UTF7_BASIC = 15;
34    const JSON = 16;
35    const LZF = 17;
36
37    /**
38     * Serialize a value.
39     *
40     * See the list of constants at the top of the file for the serializing
41     * techniques that can be used.
42     *
43     * @param mixed $data    The data to be serialized.
44     * @param mixed $mode    The mode of serialization. Can be either a single
45     *                       mode or array of modes.  If array, will be
46     *                       serialized in the order provided.
47     * @param mixed $params  Any additional parameters the serialization method
48     *                       requires.
49     *
50     * @return string  The serialized data.
51     * @throws Horde_Serialize_Exception
52     */
53    public static function serialize($data, $mode = array(self::BASIC),
54                                     $params = null)
55    {
56        if (!is_array($mode)) {
57            $mode = array($mode);
58        }
59
60        /* Parse through the list of serializing modes. */
61        foreach ($mode as $val) {
62            /* Check to make sure the mode is supported. */
63            if (!self::hasCapability($val)) {
64                throw new Horde_Serialize_Exception('Unsupported serialization type');
65            }
66            $data = self::_serialize($data, $val, $params);
67        }
68
69        return $data;
70    }
71
72    /**
73     * Unserialize a value.
74     *
75     * See the list of constants at the top of the file for the serializing
76     * techniques that can be used.
77     *
78     * @param mixed $data    The data to be unserialized.
79     * @param mixed $mode    The mode of unserialization.  Can be either a
80     *                       single mode or array of modes.  If array, will be
81     *                       unserialized in the order provided.
82     * @param mixed $params  Any additional parameters the unserialization
83     *                       method requires.
84     *
85     * @return string  The unserialized data.
86     * @throws Horde_Serialize_Exception
87     */
88    public static function unserialize($data, $mode = self::BASIC,
89                                       $params = null)
90    {
91        if (!is_array($mode)) {
92            $mode = array($mode);
93        }
94
95        /* Parse through the list of unserializing modes. */
96        foreach ($mode as $val) {
97            /* Check to make sure the mode is supported. */
98            if (!self::hasCapability($val)) {
99                throw new Horde_Serialize_Exception('Unsupported unserialization type');
100            }
101            $data = self::_unserialize($data, $val, $params);
102        }
103
104        return $data;
105    }
106
107    /**
108     * Check whether or not a serialization method is supported.
109     *
110     * @param integer $mode  The serialization method.
111     *
112     * @return boolean  True if supported, false if not.
113     */
114    public static function hasCapability($mode)
115    {
116        switch ($mode) {
117        case self::BZIP:
118            return Horde_Util::extensionExists('bz2');
119
120        case self::WDDX:
121            return Horde_Util::extensionExists('wddx');
122
123        case self::IMAPUTF7:
124            return class_exists('Horde_Imap_Client');
125
126        case self::IMAPUTF8:
127            return class_exists('Horde_Mime');
128
129        case self::GZ_DEFLATE:
130        case self::GZ_COMPRESS:
131        case self::GZ_ENCODE:
132            return Horde_Util::extensionExists('zlib');
133
134        case self::LZF:
135            return Horde_Util::extensionExists('lzf');
136
137        case self::NONE:
138        case self::BASIC:
139        case self::BASE64:
140        case self::IMAP8:
141        case self::RAW:
142        case self::URL:
143        case self::UTF7:
144        case self::UTF7_BASIC:
145        case self::JSON:
146            return true;
147
148        default:
149            return false;
150        }
151    }
152
153    /**
154     * Serialize data.
155     *
156     * @param mixed $data    The data to be serialized.
157     * @param mixed $mode    The mode of serialization. Can be
158     *                       either a single mode or array of modes.
159     *                       If array, will be serialized in the
160     *                       order provided.
161     * @param mixed $params  Any additional parameters the serialization method
162     *                       requires.
163     *
164     * @return string  A serialized string.
165     * @throws Horde_Serialize_Exception
166     */
167    protected static function _serialize($data, $mode, $params = null)
168    {
169        switch ($mode) {
170        case self::NONE:
171            break;
172
173        // $params['level'] = Level of compression (default: 3)
174        // $params['workfactor'] = How does compression phase behave when given
175        //                         worst case, highly repetitive, input data
176        //                         (default: 30)
177        case self::BZIP:
178            $data = bzcompress($data, isset($params['level']) ? $params['level'] : 3, isset($params['workfactor']) ? $params['workfactor'] : 30);
179            if (is_integer($data)) {
180                $data = false;
181            }
182            break;
183
184        case self::WDDX:
185            $data = wddx_serialize_value($data);
186            break;
187
188        case self::IMAP8:
189            $data = quoted_printable_encode($data);
190            break;
191
192        case self::IMAPUTF7:
193            $data = Horde_Imap_Client_Utf7imap::Utf8ToUtf7Imap(Horde_String::convertCharset($data, 'ISO-8859-1', 'UTF-8'));
194            break;
195
196        case self::IMAPUTF8:
197            $data = Horde_Mime::decode($data);
198            break;
199
200        // $params['level'] = Level of compression (default: 3)
201        case self::GZ_DEFLATE:
202            $data = gzdeflate($data, isset($params['level']) ? $params['level'] : 3);
203            break;
204
205        case self::BASIC:
206            $data = serialize($data);
207            break;
208
209        // $params['level'] = Level of compression (default: 3)
210        case self::GZ_COMPRESS:
211            $data = gzcompress($data, isset($params['level']) ? $params['level'] : 3);
212            break;
213
214        case self::BASE64:
215            $data = base64_encode($data);
216            break;
217
218        // $params['level'] = Level of compression (default: 3)
219        case self::GZ_ENCODE:
220            $data = gzencode($data, isset($params['level']) ? $params['level'] : 3);
221            break;
222
223        case self::RAW:
224            $data = rawurlencode($data);
225            break;
226
227        case self::URL:
228            $data = urlencode($data);
229            break;
230
231        // $params = Source character set
232        case self::UTF7:
233            $data = Horde_String::convertCharset($data, $params, 'UTF-7');
234            break;
235
236        // $params = Source character set
237        case self::UTF7_BASIC:
238            $data = self::serialize($data, array(self::UTF7, self::BASIC), $params);
239            break;
240
241        case self::JSON:
242            $tmp = json_encode($data);
243
244            /* Basic error handling attempts.
245             * TODO: JSON_ERROR_UTF8 = 5; available as of PHP 5.3.3 */
246            if (json_last_error() === 5) {
247                $data = json_encode(Horde_String::convertCharset($data, $params, 'UTF-8', true));
248            } else {
249                $data = $tmp;
250            }
251            break;
252
253        case self::LZF:
254            $data = lzf_compress($data);
255            break;
256        }
257
258        if ($data === false) {
259            throw new Horde_Serialize_Exception('Serialization failed.');
260        }
261        return $data;
262    }
263
264    /**
265     * Unserialize data.
266     *
267     * @param mixed $data    The data to be unserialized.
268     * @param mixed $mode    The mode of unserialization. Can be either a
269     *                       single mode or array of modes.  If array, will be
270     *                       unserialized in the order provided.
271     * @param mixed $params  Any additional parameters the unserialization
272     *                       method requires.
273     *
274     * @return mixed  Unserialized data.
275     * @throws Horde_Serialize_Exception
276     */
277    protected static function _unserialize(&$data, $mode, $params = null)
278    {
279        switch ($mode) {
280        case self::NONE:
281            break;
282
283        case self::RAW:
284            $data = rawurldecode($data);
285            break;
286
287        case self::URL:
288            $data = urldecode($data);
289            break;
290
291        case self::WDDX:
292            $data = wddx_deserialize($data);
293            break;
294
295        case self::BZIP:
296            // $params['small'] = Use bzip2 'small memory' mode?
297            $data = bzdecompress($data, isset($params['small']) ? $params['small'] : false);
298            break;
299
300        case self::IMAP8:
301            $data = quoted_printable_decode($data);
302            break;
303
304        case self::IMAPUTF7:
305            $data = Horde_String::convertCharset(Horde_Imap_Client_Utf7imap::Utf7ImapToUtf8($data), 'UTF-8', 'ISO-8859-1');
306            break;
307
308        case self::IMAPUTF8:
309            $data = Horde_Mime::encode($data);
310            break;
311
312        case self::BASIC:
313            $data2 = @unserialize($data);
314            // Unserialize can return false both on error and if $data is the
315            // false value.
316            if (($data2 === false) && ($data == serialize(false))) {
317                return $data2;
318            }
319            $data = $data2;
320            break;
321
322        case self::GZ_DEFLATE:
323            $data = gzinflate($data);
324            break;
325
326        case self::BASE64:
327            $data = base64_decode($data);
328            break;
329
330        case self::GZ_COMPRESS:
331            $data = gzuncompress($data);
332            break;
333
334        // $params = Output character set
335        case self::UTF7:
336            $data = Horde_String::convertCharset($data, 'utf-7', $params);
337            break;
338
339        // $params = Output character set
340        case self::UTF7_BASIC:
341            $data = self::unserialize($data, array(self::BASIC, self::UTF7), $params);
342            break;
343
344        case self::JSON:
345            $out = json_decode($data);
346            if (!is_null($out) || (strcasecmp($data, 'null') === 0)) {
347                return $out;
348            }
349            break;
350
351        case self::LZF:
352            $data = @lzf_decompress($data);
353            break;
354        }
355
356        if ($data === false) {
357            throw new Horde_Serialize_Exception('Unserialization failed.');
358        }
359
360        return $data;
361    }
362
363}
364