196 lines
5.8 KiB
PHP
196 lines
5.8 KiB
PHP
<?php
|
|
/**
|
|
* Copyright 2013-2016 Horde LLC (http://www.horde.org/)
|
|
*
|
|
* See the enclosed file COPYING for license information (LGPL). If you
|
|
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
|
|
*
|
|
* @category Horde
|
|
* @copyright 2013-2016 Horde LLC
|
|
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
|
|
* @package Pack
|
|
*/
|
|
|
|
/**
|
|
* A replacement for serialize()/json_encode() that will automatically
|
|
* use the most efficient serialization available based on the input.
|
|
*
|
|
* @author Michael Slusarz <slusarz@horde.org>
|
|
* @category Horde
|
|
* @copyright 2013-2016 Horde LLC
|
|
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
|
|
* @package Pack
|
|
*/
|
|
class Horde_Pack
|
|
{
|
|
/* Default compress length (in bytes). */
|
|
const DEFAULT_COMPRESS = 128;
|
|
|
|
/* Mask for compressed data. */
|
|
const COMPRESS_MASK = 64;
|
|
|
|
/**
|
|
* Instance of Horde_Compress_Fast shared between all instances.
|
|
*
|
|
* @var Horde_Compress_Fast
|
|
*/
|
|
protected static $_compress;
|
|
|
|
/**
|
|
* Drivers. Shared between all instances.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected static $_drivers = array();
|
|
|
|
/**
|
|
* Constructor.
|
|
*/
|
|
public function __construct()
|
|
{
|
|
if (empty(self::$_drivers)) {
|
|
$fi = new FilesystemIterator(__DIR__ . '/Pack/Driver');
|
|
$class_prefix = __CLASS__ . '_Driver_';
|
|
|
|
foreach ($fi as $val) {
|
|
if ($val->isFile()) {
|
|
$cname = $class_prefix . $val->getBasename('.php');
|
|
if (class_exists($cname) && $cname::supported()) {
|
|
$ob = new $cname();
|
|
self::$_drivers[$ob->id] = $ob;
|
|
}
|
|
}
|
|
}
|
|
|
|
krsort(self::$_drivers, SORT_NUMERIC);
|
|
|
|
self::$_compress = new Horde_Compress_Fast();
|
|
}
|
|
}
|
|
|
|
/**
|
|
*/
|
|
public function __sleep()
|
|
{
|
|
throw new LogicException('Object can not be serialized.');
|
|
}
|
|
|
|
/**
|
|
* Pack a string.
|
|
*
|
|
* @param mixed $data The data to pack.
|
|
* @param array $opts Additional options:
|
|
* <pre>
|
|
* - compress: (mixed) If false, don't use compression. If true, uses
|
|
* default compress length (DEFAULT). If 0, always compress.
|
|
* All other integer values: compress only if data is
|
|
* greater than this string length.
|
|
* - drivers: (array) Only use these drivers to pack. By default, driver
|
|
* to use is auto-determined.
|
|
* - phpob: (boolean) If true, the data contains PHP serializable
|
|
* objects (i.e. objects that have a PHP-specific serialized
|
|
* representation). If false, the data does not contain any of
|
|
* these objects. If not present, will auto-determine
|
|
* existence of these objects.
|
|
* </pre>
|
|
*
|
|
* @return string The packed string.
|
|
* @throws Horde_Pack_Exception
|
|
*/
|
|
public function pack($data, array $opts = array())
|
|
{
|
|
$opts = array_merge(array(
|
|
'compress' => true
|
|
), $opts);
|
|
|
|
if (!isset($opts['phpob'])) {
|
|
$auto = new Horde_Pack_Autodetermine($data);
|
|
$opts['phpob'] = $auto->phpob;
|
|
}
|
|
|
|
foreach (self::$_drivers as $key => $val) {
|
|
if (!empty($opts['phpob']) && !$val->phpob) {
|
|
continue;
|
|
}
|
|
|
|
if (isset($opts['drivers'])) {
|
|
if (!in_array(get_class($val), $opts['drivers'])) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* Format of data:
|
|
* First-byte:
|
|
* 1,2,4,8,16,32 - Reserved for pack format.
|
|
* 64 - Packed data has been compressed.
|
|
* 128 - RESERVED for future use (if set, indicates that initial
|
|
* byte will extend into next byte).
|
|
* Packed (and compressed data) follows this byte. */
|
|
try {
|
|
$packed = $val->pack($data);
|
|
} catch (Horde_Pack_Exception $e) {
|
|
continue;
|
|
}
|
|
|
|
if ($opts['compress'] !== false) {
|
|
if ($opts['compress'] === 0) {
|
|
$compress = true;
|
|
} else {
|
|
if ($opts['compress'] === true) {
|
|
$opts['compress'] = self::DEFAULT_COMPRESS;
|
|
}
|
|
$compress = strlen($packed) > $opts['compress'];
|
|
}
|
|
|
|
if ($compress) {
|
|
$packed = self::$_compress->compress($packed);
|
|
$key |= self::COMPRESS_MASK;
|
|
}
|
|
}
|
|
|
|
return pack('C', $key) . $packed;
|
|
}
|
|
|
|
throw new Horde_Pack_Exception('Could not pack data.');
|
|
}
|
|
|
|
/**
|
|
* Unpack a string.
|
|
*
|
|
* @param string $data The packed string.
|
|
*
|
|
* @return mixed The unpacked data.
|
|
* @throws Horde_Pack_Exception
|
|
*/
|
|
public function unpack($data)
|
|
{
|
|
if (!$data) {
|
|
return $data;
|
|
}
|
|
|
|
if (is_string($data)) {
|
|
$mask = unpack('C*', $data[0]);
|
|
$mask = reset($mask);
|
|
$data = substr($data, 1);
|
|
|
|
if ($mask & self::COMPRESS_MASK) {
|
|
$data = self::$_compress->decompress($data);
|
|
$mask ^= self::COMPRESS_MASK;
|
|
}
|
|
|
|
if (isset(self::$_drivers[$mask])) {
|
|
try {
|
|
return self::$_drivers[$mask]->unpack($data);
|
|
} catch (Horde_Pack_Exception $e) {
|
|
throw $e;
|
|
} catch (Exception $e) {
|
|
/* Unknown exceptions are handled with the throw below. */
|
|
}
|
|
}
|
|
}
|
|
|
|
throw new Horde_Pack_Exception('Could not unpack data');
|
|
}
|
|
|
|
}
|