* @author Ryan Butterfield * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License * @version $Id$ */ class Zend_Io_Reader { const MACHINE_ENDIAN_ORDER = 0; const LITTLE_ENDIAN_ORDER = 1; const BIG_ENDIAN_ORDER = 2; /** * The endianess of the current machine. * * @var integer */ private static $_endianess = 0; /** * The resource identifier of the stream. * * @var resource */ protected $_fd = null; /** * Size of the underlying stream. * * @var integer */ protected $_size = 0; /** * Constructs the Zend_Io_Reader class with given open file descriptor. * * @param resource $fd The file descriptor. * @throws Zend_Io_Exception if given file descriptor is not valid */ public function __construct($fd) { if (!is_resource($fd) || !in_array(get_resource_type($fd), array('stream'))) { require_once('Zend/Io/Exception.php'); throw new Zend_Io_Exception('Invalid resource type (only resources of type stream are supported)'); } $this->_fd = $fd; $offset = $this->getOffset(); fseek($this->_fd, 0, SEEK_END); $this->_size = ftell($this->_fd); fseek($this->_fd, $offset); } /** * Default destructor. */ public function __destruct() {} /** * Checks whether there is more to be read from the stream. Returns * true if the end has not yet been reached; false * otherwise. * * @return boolean * @throws Zend_Io_Exception if an I/O error occurs */ public function available() { return $this->getOffset() < $this->getSize(); } /** * Returns the current point of operation. * * @return integer * @throws Zend_Io_Exception if an I/O error occurs */ public function getOffset() { if ($this->_fd === null) { require_once('Zend/Io/Exception.php'); throw new Zend_Io_Exception('Cannot operate on a closed stream'); } return ftell($this->_fd); } /** * Sets the point of operation, ie the cursor offset value. The offset may * also be set to a negative value when it is interpreted as an offset from * the end of the stream instead of the beginning. * * @param integer $offset The new point of operation. * @return void * @throws Zend_Io_Exception if an I/O error occurs */ public function setOffset($offset) { if ($this->_fd === null) { require_once('Zend/Io/Exception.php'); throw new Zend_Io_Exception('Cannot operate on a closed stream'); } fseek($this->_fd, $offset < 0 ? $this->getSize() + $offset : $offset); } /** * Returns the stream size in bytes. * * @return integer */ public function getSize() { return $this->_size; } /** * Returns the underlying stream file descriptor. * * @return resource */ public function getFileDescriptor() { return $this->_fd; } /** * Jumps size amount of bytes in the stream. * * @param integer $size The amount of bytes. * @return void * @throws Zend_Io_Exception if size attribute is negative or if * an I/O error occurs */ public function skip($size) { if ($size < 0) { require_once('Zend/Io/Exception.php'); throw new Zend_Io_Exception('Size cannot be negative'); } if ($size == 0) { return; } if ($this->_fd === null) { require_once('Zend/Io/Exception.php'); throw new Zend_Io_Exception('Cannot operate on a closed stream'); } fseek($this->_fd, $size, SEEK_CUR); } /** * Reads length amount of bytes from the stream. * * @param integer $length The amount of bytes. * @return string * @throws Zend_Io_Exception if length attribute is negative or * if an I/O error occurs */ public function read($length) { if ($length < 0) { require_once('Zend/Io/Exception.php'); throw new Zend_Io_Exception('Length cannot be negative'); } if ($length == 0) { return ''; } if ($this->_fd === null) { require_once('Zend/Io/Exception.php'); throw new Zend_Io_Exception('Cannot operate on a closed stream'); } return fread($this->_fd, $length); } /** * Reads 1 byte from the stream and returns binary data as an 8-bit integer. * * @return integer * @throws Zend_Io_Exception if an I/O error occurs */ public final function readInt8() { list(, $int) = unpack('c*', $this->read(1)); return $int; } /** * Reads 1 byte from the stream and returns binary data as an unsigned 8-bit * integer. * * @return integer * @throws Zend_Io_Exception if an I/O error occurs */ public final function readUInt8() { list(, $int) = unpack('C*', $this->read(1)); return $int; } /** * Returns machine endian ordered binary data as signed 16-bit integer. * * @param string $value The binary data string. * @return integer */ private function _fromInt16($value) { list(, $int) = unpack('s*', $value); return $int; } /** * Reads 2 bytes from the stream and returns little-endian ordered binary * data as signed 16-bit integer. * * @return integer * @throws Zend_Io_Exception if an I/O error occurs */ public final function readInt16LE() { if ($this->_isBigEndian()) { return $this->_fromInt16(strrev($this->read(2))); } else { return $this->_fromInt16($this->read(2)); } } /** * Reads 2 bytes from the stream and returns big-endian ordered binary data * as signed 16-bit integer. * * @return integer * @throws Zend_Io_Exception if an I/O error occurs */ public final function readInt16BE() { if ($this->_isLittleEndian()) { return $this->_fromInt16(strrev($this->read(2))); } else { return $this->_fromInt16($this->read(2)); } } /** * Returns machine endian ordered binary data as unsigned 16-bit integer. * * @param string $value The binary data string. * @param integer $order The byte order of the binary data string. * @return integer */ private function _fromUInt16($value, $order = 0) { list(, $int) = unpack (($order == self::BIG_ENDIAN_ORDER ? 'n' : ($order == self::LITTLE_ENDIAN_ORDER ? 'v' : 'S')) . '*', $value); return $int; } /** * Reads 2 bytes from the stream and returns little-endian ordered binary * data as unsigned 16-bit integer. * * @return integer * @throws Zend_Io_Exception if an I/O error occurs */ public final function readUInt16LE() { return $this->_fromUInt16($this->read(2), self::LITTLE_ENDIAN_ORDER); } /** * Reads 2 bytes from the stream and returns big-endian ordered binary data * as unsigned 16-bit integer. * * @return integer * @throws Zend_Io_Exception if an I/O error occurs */ public final function readUInt16BE() { return $this->_fromUInt16($this->read(2), self::BIG_ENDIAN_ORDER); } /** * Returns machine-endian ordered binary data as signed 32-bit integer. * * @param string $value The binary data string. * @return integer */ private final function _fromInt32($value) { list(, $int) = unpack('l*', $value); return $int; } /** * Reads 4 bytes from the stream and returns little-endian ordered binary * data as signed 32-bit integer. * * @return integer * @throws Zend_Io_Exception if an I/O error occurs */ public final function readInt32LE() { if ($this->_isBigEndian()) return $this->_fromInt32(strrev($this->read(4))); else return $this->_fromInt32($this->read(4)); } /** * Reads 4 bytes from the stream and returns big-endian ordered binary data * as signed 32-bit integer. * * @return integer * @throws Zend_Io_Exception if an I/O error occurs */ public final function readInt32BE() { if ($this->_isLittleEndian()) return $this->_fromInt32(strrev($this->read(4))); else return $this->_fromInt32($this->read(4)); } /** * Reads 4 bytes from the stream and returns little-endian ordered binary * data as unsigned 32-bit integer. * * @return integer * @throws Zend_Io_Exception if an I/O error occurs */ public final function readUInt32LE() { if (PHP_INT_SIZE < 8) { list(, $lo, $hi) = unpack('v*', $this->read(4)); return $hi * (0xffff+1) + $lo; // eq $hi << 16 | $lo } else { list(, $int) = unpack('V*', $this->read(4)); return $int; } } /** * Reads 4 bytes from the stream and returns big-endian ordered binary data * as unsigned 32-bit integer. * * @return integer * @throws Zend_Io_Exception if an I/O error occurs */ public final function readUInt32BE() { if (PHP_INT_SIZE < 8) { list(, $hi, $lo) = unpack('n*', $this->read(4)); return $hi * (0xffff+1) + $lo; // eq $hi << 16 | $lo } else { list(, $int) = unpack('N*', $this->read(4)); return $int; } } /** * Reads 8 bytes from the stream and returns little-endian ordered binary * data as 64-bit float. * * {@internal PHP does not support 64-bit integers as the long * integer is of 32-bits but using aritmetic operations it is implicitly * converted into floating point which is of 64-bits long.} * * @return integer * @throws Zend_Io_Exception if an I/O error occurs */ public final function readInt64LE() { list(, $lolo, $lohi, $hilo, $hihi) = unpack('v*', $this->read(8)); return ($hihi * (0xffff+1) + $hilo) * (0xffffffff+1) + ($lohi * (0xffff+1) + $lolo); } /** * Reads 8 bytes from the stream and returns big-endian ordered binary data * as 64-bit float. * * {@internal PHP does not support 64-bit integers as the long integer is of * 32-bits but using aritmetic operations it is implicitly converted into * floating point which is of 64-bits long.} * * @return integer * @throws Zend_Io_Exception if an I/O error occurs */ public final function readInt64BE() { list(, $hihi, $hilo, $lohi, $lolo) = unpack('n*', $this->read(8)); return ($hihi * (0xffff+1) + $hilo) * (0xffffffff+1) + ($lohi * (0xffff+1) + $lolo); } // TODO: How to determine float size? // // /** // * Returns machine endian ordered binary data as a floating point number. // * // * @param string $value The binary data string. // * @return float // */ // private function _fromFloat($value) // { // list(, $float) = unpack('f*', $value); // return $float; // } // // /** // * Reads machine dependent size of bytes from the stream and returns // * little-endian ordered binary data as a floating point number. // * // * @return float // * @throws Zend_Io_Exception if an I/O error occurs // */ // public final function readFloatLE() // { // if ($this->_isBigEndian()) // return $this->_fromFloat(strrev($this->read(?))); // else // return $this->_fromFloat($this->read(?)); // } // // /** // * Reads machine dependent size of bytes from the stream and returns // * big-endian ordered binary data as a float point number. // * // * @return float // * @throws Zend_Io_Exception if an I/O error occurs // */ // public final function readFloatBE() // { // if ($this->_isLittleEndian()) // return $this->_fromFloat(strrev($this->read(?))); // else // return $this->_fromFloat($this->read(?)); // } /** * Reads length amount of bytes from the stream and returns * binary data as string. Removes terminating zero. * * @param integer $length The amount of bytes. * @param string $charList The list of characters you want to strip. * @return string * @throws Zend_Io_Exception if length attribute is negative or * if an I/O error occurs */ public final function readString8($length, $charList = "\0") { return rtrim($this->read($length), $charList); } /** * Reads length amount of bytes from the stream and returns * binary data as multibyte Unicode string. Removes terminating zero. * * The byte order is possibly determined from the byte order mark included * in the binary data string. The order parameter is updated if the BOM is * found. * * @param integer $length The amount of bytes. * @param integer $order The endianess of the string. * @param integer $trimOrder Whether to remove the byte order mark read the * string. * @return string * @throws Zend_Io_Exception if length attribute is negative or * if an I/O error occurs */ public final function readString16($length, &$order = null, $trimOrder = false) { $value = $this->read($length); if (strlen($value) < 2) { return ''; } if (ord($value[0]) == 0xfe && ord($value[1]) == 0xff) { $order = self::BIG_ENDIAN_ORDER; if ($trimOrder) { $value = substr($value, 2); } } if (ord($value[0]) == 0xff && ord($value[1]) == 0xfe) { $order = self::LITTLE_ENDIAN_ORDER; if ($trimOrder) { $value = substr($value, 2); } } return substr($value, -2) == "\0\0" ? substr($value, 0, -2) : $value; } /** * Reads length amount of bytes from the stream and returns * binary data as hexadecimal string having high nibble first. * * @param integer $length The amount of bytes. * @return string * @throws Zend_Io_Exception if length attribute is negative or * if an I/O error occurs */ public final function readHHex($length) { list($hex) = unpack('H*0', $this->read($length)); return $hex; } /** * Reads length amount of bytes from the stream and returns * binary data as hexadecimal string having low nibble first. * * @param integer $length The amount of bytes. * @return string * @throws Zend_Io_Exception if length attribute is negative or * if an I/O error occurs */ public final function readLHex($length) { list($hex) = unpack('h*0', $this->read($length)); return $hex; } /** * Reads 16 bytes from the stream and returns the little-endian ordered * binary data as mixed-ordered hexadecimal GUID string. * * @return string * @throws Zend_Io_Exception if an I/O error occurs */ public final function readGuid() { $C = @unpack('V1V/v2v/N2N', $this->read(16)); list($hex) = @unpack('H*0', pack ('NnnNN', $C['V'], $C['v1'], $C['v2'], $C['N1'], $C['N2'])); /* Fixes a bug in PHP versions earlier than Jan 25 2006 */ if (implode('', unpack('H*', pack('H*', 'a'))) == 'a00') { $hex = substr($hex, 0, -1); } return preg_replace ('/^(.{8})(.{4})(.{4})(.{4})/', "\\1-\\2-\\3-\\4-", $hex); } /** * Resets the stream. Attempts to reset it in some way appropriate to the * particular stream, for example by repositioning it to its starting point. * * @return void * @throws Zend_Io_Exception if an I/O error occurs */ public function reset() { if ($this->_fd === null) { require_once('Zend/Io/Exception.php'); throw new Zend_Io_Exception('Cannot operate on a closed stream'); } fseek($this->_fd, 0); } /** * Closes the stream. Once a stream has been closed, further calls to read * methods will throw an exception. Closing a previously-closed stream, * however, has no effect. * * @return void */ public function close() { if ($this->_fd !== null) { @fclose($this->_fd); $this->_fd = null; } } /** * Returns the current machine endian order. * * @return integer */ private function _getEndianess() { if (self::$_endianess === 0) { self::$_endianess = $this->_fromInt32("\x01\x00\x00\x00") == 1 ? self::LITTLE_ENDIAN_ORDER : self::BIG_ENDIAN_ORDER; } return self::$_endianess; } /** * Returns whether the current machine endian order is little endian. * * @return boolean */ private function _isLittleEndian() { return $this->_getEndianess() == self::LITTLE_ENDIAN_ORDER; } /** * Returns whether the current machine endian order is big endian. * * @return boolean */ private function _isBigEndian() { return $this->_getEndianess() == self::BIG_ENDIAN_ORDER; } /** * Magic function so that $obj->value will work. * * @param string $name The field name. * @return mixed */ public function __get($name) { if (method_exists($this, 'get' . ucfirst(strtolower($name)))) { return call_user_func (array($this, 'get' . ucfirst(strtolower($name)))); } else { require_once('Zend/Io/Exception.php'); throw new Zend_Io_Exception('Unknown field: ' . $name); } } /** * Magic function so that assignments with $obj->value will work. * * @param string $name The field name. * @param string $value The field value. * @return mixed */ public function __set($name, $value) { if (method_exists($this, 'set' . ucfirst(strtolower($name)))) { call_user_func (array($this, 'set' . ucfirst(strtolower($name))), $value); } else { require_once('Zend/Io/Exception.php'); throw new Zend_Io_Exception('Unknown field: ' . $name); } } }