12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061 |
- <?php
- /**
- * CFPropertyList
- * {@link http://developer.apple.com/documentation/Darwin/Reference/ManPages/man5/plist.5.html Property Lists}
- *
- * @author Rodney Rehm <rodney.rehm@medialize.de>
- * @author Christian Kruse <cjk@wwwtech.de>
- * @package plist
- * @version $Id$
- */
- /**
- * Facility for reading and writing binary PropertyLists. Ported from {@link
- * http://www.opensource.apple.com/source/CF/CF-476.15/CFBinaryPList.c CFBinaryPList.c}.
- *
- * @author Rodney Rehm <rodney.rehm@medialize.de>
- * @author Christian Kruse <cjk@wwwtech.de>
- * @package plist
- * @example example-read-02.php Read a Binary PropertyList
- * @example example-read-03.php Read a PropertyList without knowing the type
- */
- abstract class CFBinaryPropertyList {
- /**
- * Content of the plist (unparsed string)
- *
- * @var string
- */
- protected $content = null;
- /**
- * position in the (unparsed) string
- *
- * @var integer
- */
- protected $pos = 0;
- /**
- * Table containing uniqued objects
- *
- * @var array
- */
- protected $uniqueTable = Array();
- /**
- * Number of objects in file
- *
- * @var integer
- */
- protected $countObjects = 0;
- /**
- * The length of all strings in the file (byte length, not character length)
- *
- * @var integer
- */
- protected $stringSize = 0;
- /**
- * The length of all ints in file (byte length)
- *
- * @var integer
- */
- protected $intSize = 0;
- /**
- * The length of misc objects (i.e. not integer and not string) in file
- *
- * @var integer
- */
- protected $miscSize = 0;
- /**
- * Number of object references in file (needed to calculate reference byte length)
- *
- * @var integer
- */
- protected $objectRefs = 0;
- /**
- * Number of objects written during save phase; needed to calculate the size of the object table
- *
- * @var integer
- */
- protected $writtenObjectCount = 0;
- /**
- * Table containing all objects in the file
- */
- protected $objectTable = Array();
- /**
- * The size of object references
- */
- protected $objectRefSize = 0;
- /**
- * The „offsets” (i.e. the different entries) in the file
- */
- protected $offsets = Array();
- /**
- * Read a „null type” (filler byte, true, false, 0 byte)
- *
- * @param $length The byte itself
- *
- * @return the byte value (e.g. CFBoolean(true), CFBoolean(false), 0 or 15)
- * @throws PListException on encountering an unknown null type
- */
- protected function readBinaryNullType($length) {
- switch ($length) {
- case 0:
- return 0; // null type
- case 8:
- return new CFBoolean(false);
- case 9:
- return new CFBoolean(true);
- case 15:
- return 15; // fill type
- }
- throw new PListException("unknown null type: $length");
- }
- /**
- * Create an 64 bit integer using bcmath or gmp
- *
- * @param int $hi The higher word
- * @param int $lo The lower word
- *
- * @return mixed The integer (as int if possible, as string if not possible)
- * @throws PListException if neither gmp nor bc available
- */
- protected static function make64Int($hi, $lo) {
- // on x64, we can just use int
- if (PHP_INT_SIZE > 4) {
- return (((int)$hi) << 32) | ((int)$lo);
- }
- // lower word has to be unsigned since we don't use bitwise or, we use bcadd/gmp_add
- $lo = sprintf("%u", $lo);
- // use GMP or bcmath if possible
- if (function_exists("gmp_mul")) {
- return gmp_strval(gmp_add(gmp_mul($hi, "4294967296"), $lo));
- }
- if (function_exists("bcmul")) {
- return bcadd(bcmul($hi, "4294967296"), $lo);
- }
- if (class_exists('Math_BigInteger')) {
- $bi = new Math_BigInteger($hi);
- return $bi->multiply("4294967296")->add($lo)->toString();
- }
- throw new PListException("either gmp or bc has to be installed, or the Math_BigInteger has to be available!");
- }
- /**
- * Read an integer value
- *
- * @param integer $length The length (in bytes) of the integer value, coded as „set bit $length to 1”
- *
- * @return CFNumber The integer value
- * @throws PListException if integer val is invalid
- * @throws IOException if read error occurs
- * @uses make64Int() to overcome PHP's big integer problems
- */
- protected function readBinaryInt($length) {
- if ($length > 3) {
- throw new PListException("Integer greater than 8 bytes: $length");
- }
- $nbytes = 1 << $length;
- $val = null;
- if (strlen($buff = substr($this->content, $this->pos, $nbytes)) != $nbytes) {
- throw IOException::readError("");
- }
- $this->pos += $nbytes;
- switch ($length) {
- case 0:
- $val = unpack("C", $buff);
- $val = $val[1];
- break;
- case 1:
- $val = unpack("n", $buff);
- $val = $val[1];
- break;
- case 2:
- $val = unpack("N", $buff);
- $val = $val[1];
- break;
- case 3:
- $words = unpack("Nhighword/Nlowword", $buff);
- //$val = $words['highword'] << 32 | $words['lowword'];
- $val = self::make64Int($words['highword'], $words['lowword']);
- break;
- }
- return new CFNumber($val);
- }
- /**
- * Read a real value
- *
- * @param integer $length The length (in bytes) of the integer value, coded as „set bit $length to 1”
- *
- * @return CFNumber The real value
- * @throws PListException if real val is invalid
- * @throws IOException if read error occurs
- */
- protected function readBinaryReal($length) {
- if ($length > 3) {
- throw new PListException("Real greater than 8 bytes: $length");
- }
- $nbytes = 1 << $length;
- $val = null;
- if (strlen($buff = substr($this->content, $this->pos, $nbytes)) != $nbytes) {
- throw IOException::readError("");
- }
- $this->pos += $nbytes;
- switch ($length) {
- case 0: // 1 byte float? must be an error
- case 1: // 2 byte float? must be an error
- $x = $length + 1;
- throw new PListException("got {$x} byte float, must be an error!");
- case 2:
- $val = unpack("f", strrev($buff));
- $val = $val[1];
- break;
- case 3:
- $val = unpack("d", strrev($buff));
- $val = $val[1];
- break;
- }
- return new CFNumber($val);
- }
- /**
- * Read a date value
- *
- * @param integer $length The length (in bytes) of the integer value, coded as „set bit $length to 1”
- *
- * @return CFDate The date value
- * @throws PListException if date val is invalid
- * @throws IOException if read error occurs
- */
- protected function readBinaryDate($length) {
- if ($length > 3) {
- throw new PListException("Date greater than 8 bytes: $length");
- }
- $nbytes = 1 << $length;
- $val = null;
- if (strlen($buff = substr($this->content, $this->pos, $nbytes)) != $nbytes) {
- throw IOException::readError("");
- }
- $this->pos += $nbytes;
- switch ($length) {
- case 0: // 1 byte CFDate is an error
- case 1: // 2 byte CFDate is an error
- $x = $length + 1;
- throw new PListException("{$x} byte CFdate, error");
- case 2:
- $val = unpack("f", strrev($buff));
- $val = $val[1];
- break;
- case 3:
- $val = unpack("d", strrev($buff));
- $val = $val[1];
- break;
- }
- return new CFDate($val, CFDate::TIMESTAMP_APPLE);
- }
- /**
- * Read a data value
- *
- * @param integer $length The length (in bytes) of the integer value, coded as „set bit $length to 1”
- *
- * @return CFData The data value
- * @throws IOException if read error occurs
- */
- protected function readBinaryData($length) {
- if ($length == 0) {
- $buff = "";
- } else {
- $buff = substr($this->content, $this->pos, $length);
- if (strlen($buff) != $length) {
- throw IOException::readError("");
- }
- $this->pos += $length;
- }
- return new CFData($buff, false);
- }
- /**
- * Read a string value, usually coded as utf8
- *
- * @param integer $length The length (in bytes) of the string value
- *
- * @return CFString The string value, utf8 encoded
- * @throws IOException if read error occurs
- */
- protected function readBinaryString($length) {
- if ($length == 0) {
- $buff = "";
- } else {
- if (strlen($buff = substr($this->content, $this->pos, $length)) != $length) {
- throw IOException::readError("");
- }
- $this->pos += $length;
- }
- if (!isset($this->uniqueTable[$buff])) {
- $this->uniqueTable[$buff] = true;
- }
- return new CFString($buff);
- }
- /**
- * Convert the given string from one charset to another.
- * Trying to use MBString, Iconv, Recode - in that particular order.
- *
- * @param string $string the string to convert
- * @param string $fromCharset the charset the given string is currently encoded in
- * @param string $toCharset the charset to convert to, defaults to UTF-8
- *
- * @return string the converted string
- * @throws PListException on neither MBString, Iconv, Recode being available
- */
- public static function convertCharset($string, $fromCharset, $toCharset = 'UTF-8') {
- if (function_exists('mb_convert_encoding')) {
- return mb_convert_encoding($string, $toCharset, $fromCharset);
- }
- if (function_exists('iconv')) {
- return iconv($fromCharset, $toCharset, $string);
- }
- if (function_exists('recode_string')) {
- return recode_string($fromCharset.'..'.$toCharset, $string);
- }
- throw new PListException('neither iconv nor mbstring supported. how are we supposed to work on strings here?');
- }
- /**
- * Count characters considering character set
- * Trying to use MBString, Iconv - in that particular order.
- *
- * @param string $string the string to convert
- * @param string $charset the charset the given string is currently encoded in
- *
- * @return integer The number of characters in that string
- * @throws PListException on neither MBString, Iconv being available
- */
- public static function charsetStrlen($string, $charset = "UTF-8") {
- if (function_exists('mb_strlen')) {
- return mb_strlen($string, $charset);
- }
- if (function_exists('iconv_strlen')) {
- return iconv_strlen($string, $charset);
- }
- throw new PListException('neither iconv nor mbstring supported. how are we supposed to work on strings here?');
- }
- /**
- * Read a unicode string value, coded as UTF-16BE
- *
- * @param integer $length The length (in bytes) of the string value
- *
- * @return CFString The string value, utf8 encoded
- * @throws IOException if read error occurs
- */
- protected function readBinaryUnicodeString($length) {
- /* The problem is: we get the length of the string IN CHARACTERS;
- since a char in UTF-16 can be 16 or 32 bit long, we don't really know
- how long the string is in bytes */
- if (strlen($buff = substr($this->content, $this->pos, 2 * $length)) != 2 * $length) {
- throw IOException::readError("");
- }
- $this->pos += 2 * $length;
- if (!isset($this->uniqueTable[$buff])) {
- $this->uniqueTable[$buff] = true;
- }
- return new CFString(self::convertCharset($buff, "UTF-16BE", "UTF-8"));
- }
- /**
- * Read an array value, including contained objects
- *
- * @param integer $length The number of contained objects
- *
- * @return CFArray The array value, including the objects
- * @throws IOException if read error occurs
- */
- protected function readBinaryArray($length) {
- $ary = new CFArray();
- // first: read object refs
- if ($length != 0) {
- if (strlen($buff = substr($this->content, $this->pos, $length * $this->objectRefSize)) != $length
- * $this->objectRefSize
- ) {
- throw IOException::readError("");
- }
- $this->pos += $length * $this->objectRefSize;
- $objects = unpack($this->objectRefSize == 1 ? "C*" : "n*", $buff);
- // now: read objects
- for ($i = 0; $i < $length; ++$i) {
- $object = $this->readBinaryObjectAt($objects[$i + 1] + 1, $this->objectRefSize);
- $ary->add($object);
- }
- }
- return $ary;
- }
- /**
- * Read a dictionary value, including contained objects
- *
- * @param integer $length The number of contained objects
- *
- * @return CFDictionary The dictionary value, including the objects
- * @throws IOException if read error occurs
- */
- protected function readBinaryDict($length) {
- $dict = new CFDictionary();
- // first: read keys
- if ($length != 0) {
- if (strlen($buff = substr($this->content, $this->pos, $length * $this->objectRefSize)) != $length
- * $this->objectRefSize
- ) {
- throw IOException::readError("");
- }
- $this->pos += $length * $this->objectRefSize;
- $keys = unpack(($this->objectRefSize == 1 ? "C*" : "n*"), $buff);
- // second: read object refs
- if (strlen($buff = substr($this->content, $this->pos, $length * $this->objectRefSize)) != $length
- * $this->objectRefSize
- ) {
- throw IOException::readError("");
- }
- $this->pos += $length * $this->objectRefSize;
- $objects = unpack(($this->objectRefSize == 1 ? "C*" : "n*"), $buff);
- // read real keys and objects
- for ($i = 0; $i < $length; ++$i) {
- $key = $this->readBinaryObjectAt($keys[$i + 1] + 1);
- $object = $this->readBinaryObjectAt($objects[$i + 1] + 1);
- $dict->add($key->getValue(), $object);
- }
- }
- return $dict;
- }
- /**
- * Read an object type byte, decode it and delegate to the correct reader function
- *
- * @return mixed The value of the delegate reader, so any of the CFType subclasses
- * @throws IOException if read error occurs
- */
- function readBinaryObject() {
- // first: read the marker byte
- if (strlen($buff = substr($this->content, $this->pos, 1)) != 1) {
- throw IOException::readError("");
- }
- $this->pos++;
- $object_length = unpack("C*", $buff);
- $object_length = $object_length[1] & 0xF;
- $buff = unpack("H*", $buff);
- $buff = $buff[1];
- $object_type = substr($buff, 0, 1);
- if ($object_type != "0" && $object_length == 15) {
- $object_length = $this->readBinaryObject($this->objectRefSize);
- $object_length = $object_length->getValue();
- }
- $retval = null;
- switch ($object_type) {
- case '0': // null, false, true, fillbyte
- $retval = $this->readBinaryNullType($object_length);
- break;
- case '1': // integer
- $retval = $this->readBinaryInt($object_length);
- break;
- case '2': // real
- $retval = $this->readBinaryReal($object_length);
- break;
- case '3': // date
- $retval = $this->readBinaryDate($object_length);
- break;
- case '4': // data
- $retval = $this->readBinaryData($object_length);
- break;
- case '5': // byte string, usually utf8 encoded
- $retval = $this->readBinaryString($object_length);
- break;
- case '6': // unicode string (utf16be)
- $retval = $this->readBinaryUnicodeString($object_length);
- break;
- case 'a': // array
- $retval = $this->readBinaryArray($object_length);
- break;
- case 'd': // dictionary
- $retval = $this->readBinaryDict($object_length);
- break;
- }
- return $retval;
- }
- /**
- * Read an object type byte at position $pos, decode it and delegate to the correct reader function
- *
- * @param integer $pos The table position in the offsets table
- *
- * @return mixed The value of the delegate reader, so any of the CFType subclasses
- */
- function readBinaryObjectAt($pos) {
- $this->pos = $this->offsets[$pos];
- return $this->readBinaryObject();
- }
- /**
- * Parse a binary plist string
- *
- * @return void
- * @throws IOException if read error occurs
- */
- public function parseBinaryString() {
- $this->uniqueTable = Array();
- $this->countObjects = 0;
- $this->stringSize = 0;
- $this->intSize = 0;
- $this->miscSize = 0;
- $this->objectRefs = 0;
- $this->writtenObjectCount = 0;
- $this->objectTable = Array();
- $this->objectRefSize = 0;
- $this->offsets = Array();
- // first, we read the trailer: 32 byte from the end
- $buff = substr($this->content, -32);
- $infos = unpack(
- "x6/Coffset_size/Cobject_ref_size/x4/Nnumber_of_objects/x4/Ntop_object/x4/Ntable_offset", $buff
- );
- // after that, get the offset table
- $coded_offset_table = substr(
- $this->content, $infos['table_offset'], $infos['number_of_objects'] * $infos['offset_size']
- );
- if (strlen($coded_offset_table) != $infos['number_of_objects'] * $infos['offset_size']) {
- throw IOException::readError("");
- }
- $this->countObjects = $infos['number_of_objects'];
- // decode offset table
- $formats = Array("", "C*", "n*", null, "N*");
- if ($infos['offset_size'] == 3) { # since PHP does not support parenthesis in pack/unpack expressions,
- # "(H6)*" does not work and we have to work round this by repeating the
- # expression as often as it fits in the string
- $this->offsets = array(null);
- while ($coded_offset_table) {
- $str = unpack("H6", $coded_offset_table);
- $this->offsets[] = hexdec($str[1]);
- $coded_offset_table = substr($coded_offset_table, 3);
- }
- } else {
- $this->offsets = unpack($formats[$infos['offset_size']], $coded_offset_table);
- }
- $this->uniqueTable = Array();
- $this->objectRefSize = $infos['object_ref_size'];
- $top = $this->readBinaryObjectAt($infos['top_object'] + 1);
- $this->add($top);
- }
- /**
- * Read a binary plist stream
- *
- * @param resource $stream The stream to read
- *
- * @return void
- * @throws IOException if read error occurs
- */
- function readBinaryStream($stream) {
- $str = stream_get_contents($stream);
- $this->parseBinary($str);
- }
- /**
- * parse a binary plist string
- *
- * @param string $content The stream to read, defaults to {@link $this->content}
- *
- * @return void
- * @throws IOException if read error occurs
- */
- function parseBinary($content = null) {
- if ($content !== null) {
- $this->content = $content;
- }
- $this->pos = 0;
- $this->parseBinaryString();
- }
- /**
- * Read a binary plist file
- *
- * @param string $file The file to read / or the handle of file
- *
- * @return void
- * @throws IOException if read error occurs
- */
- function readBinary($file) {
- if (is_resource($file)) {
- $fd = $file;
- } else {
- if (!($fd = fopen($file, "rb"))) {
- throw new IOException("Could not open file {$file}!");
- }
- }
- $this->readBinaryStream($fd);
- fclose($fd);
- }
- /**
- * calculate the bytes needed for a size integer value
- *
- * @param integer $int The integer value to calculate
- *
- * @return integer The number of bytes needed
- */
- public static function bytesSizeInt($int) {
- $nbytes = 0;
- if ($int > 0xE) {
- $nbytes += 2;
- } // 2 size-bytes
- if ($int > 0xFF) {
- $nbytes += 1;
- } // 3 size-bytes
- if ($int > 0xFFFF) {
- $nbytes += 2;
- } // 5 size-bytes
- return $nbytes;
- }
- /**
- * Calculate the byte needed for a „normal” integer value
- *
- * @param integer $int The integer value
- *
- * @return integer The number of bytes needed + 1 (because of the „marker byte”)
- */
- public static function bytesInt($int) {
- $nbytes = 1;
- if ($int > 0xFF) {
- $nbytes += 1;
- } // 2 byte integer
- if ($int > 0xFFFF) {
- $nbytes += 2;
- } // 4 byte integer
- if ($int > 0xFFFFFFFF) {
- $nbytes += 4;
- } // 8 byte integer
- if ($int < 0) {
- $nbytes += 7;
- } // 8 byte integer (since it is signed)
- return $nbytes + 1; // one „marker” byte
- }
- /**
- * „pack” a value (i.e. write the binary representation as big endian to a string) with the specified size
- *
- * @param integer $nbytes The number of bytes to pack
- * @param integer $int the integer value to pack
- *
- * @return string The packed value as string
- */
- public static function packItWithSize($nbytes, $int) {
- $formats = Array("C", "n", "N", "N");
- $format = $formats[$nbytes - 1];
- $ret = '';
- if ($nbytes == 3) {
- return substr(pack($format, $int), -3);
- }
- return pack($format, $int);
- }
- /**
- * Calculate the bytes needed to save the number of objects
- *
- * @param integer $count_objects The number of objects
- *
- * @return integer The number of bytes
- */
- public static function bytesNeeded($count_objects) {
- $nbytes = 0;
- while ($count_objects >= 1) {
- $nbytes++;
- $count_objects /= 256;
- }
- return $nbytes;
- }
- /**
- * Code an integer to byte representation
- *
- * @param integer $int The integer value
- *
- * @return string The packed byte value
- */
- public static function intBytes($int) {
- $intbytes = "";
- if ($int > 0xFFFF) {
- $intbytes = "\x12".pack("N", $int);
- } // 4 byte integer
- elseif ($int > 0xFF) {
- $intbytes = "\x11".pack("n", $int);
- } // 2 byte integer
- else {
- $intbytes = "\x10".pack("C", $int);
- } // 8 byte integer
- return $intbytes;
- }
- /**
- * Code an type byte, consisting of the type marker and the length of the type
- *
- * @param string $type The type byte value (i.e. "d" for dictionaries)
- * @param integer $type_len The length of the type
- *
- * @return string The packed type byte value
- */
- public static function typeBytes($type, $type_len) {
- $optional_int = "";
- if ($type_len < 15) {
- $type .= sprintf("%x", $type_len);
- } else {
- $type .= "f";
- $optional_int = self::intBytes($type_len);
- }
- return pack("H*", $type).$optional_int;
- }
- /**
- * Count number of objects and create a unique table for strings
- *
- * @param $value The value to count and unique
- *
- * @return void
- */
- protected function uniqueAndCountValues($value) {
- // no uniquing for other types than CFString and CFData
- if ($value instanceof CFNumber) {
- $val = $value->getValue();
- if (intval($val) == $val && !is_float($val) && strpos($val, '.') === false) {
- $this->intSize += self::bytesInt($val);
- } else {
- $this->miscSize += 9;
- } // 9 bytes (8 + marker byte) for real
- $this->countObjects++;
- return;
- } elseif ($value instanceof CFDate) {
- $this->miscSize += 9; // since date in plist is real, we need 9 byte (8 + marker byte)
- $this->countObjects++;
- return;
- } elseif ($value instanceof CFBoolean) {
- $this->countObjects++;
- $this->miscSize += 1;
- return;
- } elseif ($value instanceof CFArray) {
- $cnt = 0;
- foreach ($value as $v) {
- ++$cnt;
- $this->uniqueAndCountValues($v);
- $this->objectRefs++; // each array member is a ref
- }
- $this->countObjects++;
- $this->intSize += self::bytesSizeInt($cnt);
- $this->miscSize++; // marker byte for array
- return;
- } elseif ($value instanceof CFDictionary) {
- $cnt = 0;
- foreach ($value as $k => $v) {
- ++$cnt;
- if (!isset($this->uniqueTable[$k])) {
- $this->uniqueTable[$k] = 0;
- $len = self::binaryStrlen($k);
- $this->stringSize += $len + 1;
- $this->intSize += self::bytesSizeInt(self::charsetStrlen($k, 'UTF-8'));
- }
- $this->objectRefs += 2; // both, key and value, are refs
- $this->uniqueTable[$k]++;
- $this->uniqueAndCountValues($v);
- }
- $this->countObjects++;
- $this->miscSize++; // marker byte for dict
- $this->intSize += self::bytesSizeInt($cnt);
- return;
- } elseif ($value instanceOf CFData) {
- $val = $value->getValue();
- $len = strlen($val);
- $this->intSize += self::bytesSizeInt($len);
- $this->miscSize += $len + 1;
- $this->countObjects++;
- return;
- } else {
- $val = $value->getValue();
- }
- if (!isset($this->uniqueTable[$val])) {
- $this->uniqueTable[$val] = 0;
- $len = self::binaryStrlen($val);
- $this->stringSize += $len + 1;
- $this->intSize += self::bytesSizeInt(self::charsetStrlen($val, 'UTF-8'));
- }
- $this->uniqueTable[$val]++;
- }
- /**
- * Convert CFPropertyList to binary format; since we have to count our objects we simply unique CFDictionary and
- * CFArray
- *
- * @return string The binary plist content
- */
- public function toBinary() {
- $this->uniqueTable = Array();
- $this->countObjects = 0;
- $this->stringSize = 0;
- $this->intSize = 0;
- $this->miscSize = 0;
- $this->objectRefs = 0;
- $this->writtenObjectCount = 0;
- $this->objectTable = Array();
- $this->objectRefSize = 0;
- $this->offsets = Array();
- $binary_str = "bplist00";
- $value = $this->getValue(true);
- $this->uniqueAndCountValues($value);
- $this->countObjects += count($this->uniqueTable);
- $this->objectRefSize = self::bytesNeeded($this->countObjects);
- $file_size = $this->stringSize + $this->intSize + $this->miscSize + $this->objectRefs * $this->objectRefSize
- + 40;
- $offset_size = self::bytesNeeded($file_size);
- $table_offset = $file_size - 32;
- $this->objectTable = Array();
- $this->writtenObjectCount = 0;
- $this->uniqueTable = Array(); // we needed it to calculate several values
- $value->toBinary($this);
- $object_offset = 8;
- $offsets = Array();
- for ($i = 0; $i < count($this->objectTable); ++$i) {
- $binary_str .= $this->objectTable[$i];
- $offsets[$i] = $object_offset;
- $object_offset += strlen($this->objectTable[$i]);
- }
- for ($i = 0; $i < count($offsets); ++$i) {
- $binary_str .= self::packItWithSize($offset_size, $offsets[$i]);
- }
- $binary_str .= pack("x6CC", $offset_size, $this->objectRefSize);
- $binary_str .= pack("x4N", $this->countObjects);
- $binary_str .= pack("x4N", 0);
- $binary_str .= pack("x4N", $table_offset);
- return $binary_str;
- }
- /**
- * Counts the number of bytes the string will have when coded; utf-16be if non-ascii characters are present.
- *
- * @param string $val The string value
- *
- * @return integer The length of the coded string in bytes
- */
- protected static function binaryStrlen($val) {
- for ($i = 0; $i < strlen($val); ++$i) {
- if (ord($val{$i}) >= 128) {
- $val = self::convertCharset($val, 'UTF-8', 'UTF-16BE');
- return strlen($val);
- }
- }
- return strlen($val);
- }
- /**
- * Uniques and transforms a string value to binary format and adds it to the object table
- *
- * @param string $val The string value
- *
- * @return integer The position in the object table
- */
- public function stringToBinary($val) {
- $saved_object_count = -1;
- if (!isset($this->uniqueTable[$val])) {
- $saved_object_count = $this->writtenObjectCount++;
- $this->uniqueTable[$val] = $saved_object_count;
- $utf16 = false;
- for ($i = 0; $i < strlen($val); ++$i) {
- if (ord($val{$i}) >= 128) {
- $utf16 = true;
- break;
- }
- }
- if ($utf16) {
- $bdata = self::typeBytes("6", mb_strlen($val, 'UTF-8')); // 6 is 0110, unicode string (utf16be)
- $val = self::convertCharset($val, 'UTF-8', 'UTF-16BE');
- $this->objectTable[$saved_object_count] = $bdata.$val;
- } else {
- $bdata = self::typeBytes(
- "5", strlen($val)
- ); // 5 is 0101 which is an ASCII string (seems to be ASCII encoded)
- $this->objectTable[$saved_object_count] = $bdata.$val;
- }
- } else {
- $saved_object_count = $this->uniqueTable[$val];
- }
- return $saved_object_count;
- }
- /**
- * Codes an integer to binary format
- *
- * @param integer $value The integer value
- *
- * @return string the coded integer
- */
- protected function intToBinary($value) {
- $nbytes = 0;
- if ($value > 0xFF) {
- $nbytes = 1;
- } // 1 byte integer
- if ($value > 0xFFFF) {
- $nbytes += 1;
- } // 4 byte integer
- if ($value > 0xFFFFFFFF) {
- $nbytes += 1;
- } // 8 byte integer
- if ($value < 0) {
- $nbytes = 3;
- } // 8 byte integer, since signed
- $bdata = self::typeBytes("1", $nbytes); // 1 is 0001, type indicator for integer
- $buff = "";
- if ($nbytes < 3) {
- if ($nbytes == 0) {
- $fmt = "C";
- } elseif ($nbytes == 1) {
- $fmt = "n";
- } else {
- $fmt = "N";
- }
- $buff = pack($fmt, $value);
- } else {
- if (PHP_INT_SIZE > 4) {
- // 64 bit signed integer; we need the higher and the lower 32 bit of the value
- $high_word = $value >> 32;
- $low_word = $value & 0xFFFFFFFF;
- } else {
- // since PHP can only handle 32bit signed, we can only get 32bit signed values at this point - values above 0x7FFFFFFF are
- // floats. So we ignore the existance of 64bit on non-64bit-machines
- if ($value < 0) {
- $high_word = 0xFFFFFFFF;
- } else {
- $high_word = 0;
- }
- $low_word = $value;
- }
- $buff = pack("N", $high_word).pack("N", $low_word);
- }
- return $bdata.$buff;
- }
- /**
- * Codes a real value to binary format
- *
- * @param float $val The real value
- *
- * @return string The coded real
- */
- protected function realToBinary($val) {
- $bdata = self::typeBytes("2", 3); // 2 is 0010, type indicator for reals
- return $bdata.strrev(pack("d", (float)$val));
- }
- /**
- * Converts a numeric value to binary and adds it to the object table
- *
- * @param numeric $value The numeric value
- *
- * @return integer The position in the object table
- */
- public function numToBinary($value) {
- $saved_object_count = $this->writtenObjectCount++;
- $val = "";
- if (intval($value) == $value && !is_float($value) && strpos($value, '.') === false) {
- $val = $this->intToBinary($value);
- } else {
- $val = $this->realToBinary($value);
- }
- $this->objectTable[$saved_object_count] = $val;
- return $saved_object_count;
- }
- /**
- * Convert date value (apple format) to binary and adds it to the object table
- *
- * @param integer $value The date value
- *
- * @return integer The position of the coded value in the object table
- */
- public function dateToBinary($val) {
- $saved_object_count = $this->writtenObjectCount++;
- $hour = gmdate("H", $val);
- $min = gmdate("i", $val);
- $sec = gmdate("s", $val);
- $mday = gmdate("j", $val);
- $mon = gmdate("n", $val);
- $year = gmdate("Y", $val);
- $val = gmmktime($hour, $min, $sec, $mon, $mday, $year)
- - CFDate::DATE_DIFF_APPLE_UNIX; // CFDate is a real, number of seconds since 01/01/2001 00:00:00 GMT
- $bdata = self::typeBytes("3", 3); // 3 is 0011, type indicator for date
- $this->objectTable[$saved_object_count] = $bdata.strrev(pack("d", $val));
- return $saved_object_count;
- }
- /**
- * Convert a bool value to binary and add it to the object table
- *
- * @param bool $val The boolean value
- *
- * @return integer The position in the object table
- */
- public function boolToBinary($val) {
- $saved_object_count = $this->writtenObjectCount++;
- $this->objectTable[$saved_object_count] = $val ? "\x9"
- : "\x8"; // 0x9 is 1001, type indicator for true; 0x8 is 1000, type indicator for false
- return $saved_object_count;
- }
- /**
- * Convert data value to binary format and add it to the object table
- *
- * @param string $val The data value
- *
- * @return integer The position in the object table
- */
- public function dataToBinary($val) {
- $saved_object_count = $this->writtenObjectCount++;
- $bdata = self::typeBytes("4", strlen($val)); // a is 1000, type indicator for data
- $this->objectTable[$saved_object_count] = $bdata.$val;
- return $saved_object_count;
- }
- /**
- * Convert array to binary format and add it to the object table
- *
- * @param CFArray $val The array to convert
- *
- * @return integer The position in the object table
- */
- public function arrayToBinary($val) {
- $saved_object_count = $this->writtenObjectCount++;
- $bdata = self::typeBytes("a", count($val->getValue())); // a is 1010, type indicator for arrays
- foreach ($val as $v) {
- $bval = $v->toBinary($this);
- $bdata .= self::packItWithSize($this->objectRefSize, $bval);
- }
- $this->objectTable[$saved_object_count] = $bdata;
- return $saved_object_count;
- }
- /**
- * Convert dictionary to binary format and add it to the object table
- *
- * @param CFDictionary $val The dict to convert
- *
- * @return integer The position in the object table
- */
- public function dictToBinary($val) {
- $saved_object_count = $this->writtenObjectCount++;
- $bdata = self::typeBytes("d", count($val->getValue())); // d=1101, type indicator for dictionary
- foreach ($val as $k => $v) {
- $str = new CFString($k);
- $key = $str->toBinary($this);
- $bdata .= self::packItWithSize($this->objectRefSize, $key);
- }
- foreach ($val as $k => $v) {
- $bval = $v->toBinary($this);
- $bdata .= self::packItWithSize($this->objectRefSize, $bval);
- }
- $this->objectTable[$saved_object_count] = $bdata;
- return $saved_object_count;
- }
- }
|