CFBinaryPropertyList.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895
  1. <?php
  2. /**
  3. * CFPropertyList
  4. * {@link http://developer.apple.com/documentation/Darwin/Reference/ManPages/man5/plist.5.html Property Lists}
  5. * @author Rodney Rehm <[email protected]>
  6. * @author Christian Kruse <[email protected]>
  7. * @package plist
  8. * @version $Id$
  9. */
  10. /**
  11. * Facility for reading and writing binary PropertyLists. Ported from {@link http://www.opensource.apple.com/source/CF/CF-476.15/CFBinaryPList.c CFBinaryPList.c}.
  12. * @author Rodney Rehm <[email protected]>
  13. * @author Christian Kruse <[email protected]>
  14. * @package plist
  15. * @example example-read-02.php Read a Binary PropertyList
  16. * @example example-read-03.php Read a PropertyList without knowing the type
  17. */
  18. abstract class CFBinaryPropertyList {
  19. /**
  20. * Table containing uniqued objects
  21. * @var array
  22. */
  23. protected $uniqueTable = Array();
  24. /**
  25. * Number of objects in file
  26. * @var integer
  27. */
  28. protected $countObjects = 0;
  29. /**
  30. * The length of all strings in the file (byte length, not character length)
  31. * @var integer
  32. */
  33. protected $stringSize = 0;
  34. /**
  35. * The length of all ints in file (byte length)
  36. * @var integer
  37. */
  38. protected $intSize = 0;
  39. /**
  40. * The length of misc objects (i.e. not integer and not string) in file
  41. * @var integer
  42. */
  43. protected $miscSize = 0;
  44. /**
  45. * Number of object references in file (needed to calculate reference byte length)
  46. * @var integer
  47. */
  48. protected $objectRefs = 0;
  49. /**
  50. * Number of objects written during save phase; needed to calculate the size of the object table
  51. * @var integer
  52. */
  53. protected $writtenObjectCount = 0;
  54. /**
  55. * Table containing all objects in the file
  56. */
  57. protected $objectTable = Array();
  58. /**
  59. * The size of object references
  60. */
  61. protected $objectRefSize = 0;
  62. /**
  63. * The „offsets” (i.e. the different entries) in the file
  64. */
  65. protected $offsets = Array();
  66. /**
  67. * Read a „null type” (filler byte, true, false, 0 byte)
  68. * @param $length The byte itself
  69. * @return the byte value (e.g. CFBoolean(true), CFBoolean(false), 0 or 15)
  70. * @throws PListException on encountering an unknown null type
  71. */
  72. protected function readBinaryNullType($length) {
  73. switch($length) {
  74. case 0: return 0; // null type
  75. case 8: return new CFBoolean(false);
  76. case 9: return new CFBoolean(true);
  77. case 15: return 15; // fill type
  78. }
  79. throw new PListException("unknown null type: $length");
  80. }
  81. /**
  82. * Create an 64 bit integer using bcmath or gmp
  83. * @param int $hi The higher word
  84. * @param int $lo The lower word
  85. * @return mixed The integer (as int if possible, as string if not possible)
  86. * @throws PListException if neither gmp nor bc available
  87. */
  88. protected static function make64Int($hi,$lo) {
  89. // on x64, we can just use int
  90. if(PHP_INT_SIZE > 4) return (((int)$hi)<<32) | ((int)$lo);
  91. // lower word has to be unsigned since we don't use bitwise or, we use bcadd/gmp_add
  92. $lo = sprintf("%u", $lo);
  93. // use GMP or bcmath if possible
  94. if(function_exists("gmp_mul")) return gmp_strval(gmp_add(gmp_mul($hi, "4294967296"), $lo));
  95. if(function_exists("bcmul")) return bcadd(bcmul($hi,"4294967296"), $lo);
  96. throw new PListException("either gmp or bc has to be installed!");
  97. }
  98. /**
  99. * Read an integer value
  100. * @param string $fname The filename of the file we're reading from
  101. * @param ressource $fd The file descriptor we're reading from
  102. * @param integer $length The length (in bytes) of the integer value, coded as „set bit $length to 1”
  103. * @return CFNumber The integer value
  104. * @throws PListException if integer val is invalid
  105. * @throws IOException if read error occurs
  106. */
  107. protected function readBinaryInt($fname,$fd,$length) {
  108. if($length > 3) throw new PListException("Integer greater than 8 bytes: $length");
  109. $nbytes = 1 << $length;
  110. $val = null;
  111. if(strlen($buff = fread($fd, $nbytes)) != $nbytes) throw IOException::readError($fname);
  112. switch($length) {
  113. case 0:
  114. $val = unpack("C", $buff);
  115. $val = $val[1];
  116. break;
  117. case 1:
  118. $val = unpack("n", $buff);
  119. $val = $val[1];
  120. break;
  121. case 2:
  122. $val = unpack("N", $buff);
  123. $val = $val[1];
  124. break;
  125. case 3:
  126. $words = unpack("Nhighword/Nlowword",$buff);
  127. //$val = $words['highword'] << 32 | $words['lowword'];
  128. $val = self::make64Int($words['highword'],$words['lowword']);
  129. break;
  130. }
  131. return new CFNumber($val);
  132. }
  133. /**
  134. * Read a real value
  135. * @param string $fname The filename of the file we're reading from
  136. * @param ressource $fd The file descriptor we're reading from
  137. * @param integer $length The length (in bytes) of the integer value, coded as „set bit $length to 1”
  138. * @return CFNumber The real value
  139. * @throws PListException if real val is invalid
  140. * @throws IOException if read error occurs
  141. */
  142. protected function readBinaryReal($fname,$fd,$length) {
  143. if($length > 3) throw new PListException("Real greater than 8 bytes: $length");
  144. $nbytes = 1 << $length;
  145. $val = null;
  146. if(strlen($buff = fread($fd, $nbytes)) != $nbytes) throw IOException::readError($fname);
  147. switch($length) {
  148. case 0: // 1 byte float? must be an error
  149. case 1: // 2 byte float? must be an error
  150. $x = $length + 1;
  151. throw new PListException("got {$x} byte float, must be an error!");
  152. case 2:
  153. $val = unpack("f", strrev($buff));
  154. $val = $val[1];
  155. break;
  156. case 3:
  157. $val = unpack("d", strrev($buff));
  158. $val = $val[1];
  159. break;
  160. }
  161. return new CFNumber($val);
  162. }
  163. /**
  164. * Read a date value
  165. * @param string $fname The filename of the file we're reading from
  166. * @param ressource $fd The file descriptor we're reading from
  167. * @param integer $length The length (in bytes) of the integer value, coded as „set bit $length to 1”
  168. * @return CFDate The date value
  169. * @throws PListException if date val is invalid
  170. * @throws IOException if read error occurs
  171. */
  172. protected function readBinaryDate($fname,$fd,$length) {
  173. if($length > 3) throw new PListException("Date greater than 8 bytes: $length");
  174. $nbytes = 1 << $length;
  175. $val = null;
  176. if(strlen($buff = fread($fd, $nbytes)) != $nbytes) throw IOException::readError($fname);
  177. switch($length) {
  178. case 0: // 1 byte CFDate is an error
  179. case 1: // 2 byte CFDate is an error
  180. $x = $length + 1;
  181. throw new PListException("{$x} byte CFdate, error");
  182. case 2:
  183. $val = unpack("f", strrev($buff));
  184. $val = $val[1];
  185. break;
  186. case 3:
  187. $val = unpack("d", strrev($buff));
  188. $val = $val[1];
  189. break;
  190. }
  191. return new CFDate($val,CFDate::TIMESTAMP_APPLE);
  192. }
  193. /**
  194. * Read a data value
  195. * @param string $fname The filename of the file we're reading from
  196. * @param ressource $fd The file descriptor we're reading from
  197. * @param integer $length The length (in bytes) of the integer value, coded as „set bit $length to 1”
  198. * @return CFData The data value
  199. * @throws IOException if read error occurs
  200. */
  201. protected function readBinaryData($fname,$fd,$length) {
  202. if(strlen($buff = fread($fd, $length)) != $length) throw IOException::readError($fname);
  203. return new CFData($buff,false);
  204. }
  205. /**
  206. * Read a string value, usually coded as utf8
  207. * @param string $fname The filename of the file we're reading from
  208. * @param ressource $fd The file descriptor we're reading from
  209. * @param integer $length The length (in bytes) of the string value
  210. * @return CFString The string value, utf8 encoded
  211. * @throws IOException if read error occurs
  212. */
  213. protected function readBinaryString($fname,$fd,$length) {
  214. if(strlen($buff = fread($fd, $length)) != $length) throw IOException::readError($fname);
  215. if(!isset($this->uniqueTable[$buff])) $this->uniqueTable[$buff] = true;
  216. return new CFString($buff);
  217. }
  218. /**
  219. * Convert the given string from one charset to another.
  220. * Trying to use MBString, Iconv, Recode - in that particular order.
  221. * @param string $string the string to convert
  222. * @param string $fromCharset the charset the given string is currently encoded in
  223. * @param string $toCharset the charset to convert to, defaults to UTF-8
  224. * @return string the converted string
  225. * @throws PListException on neither MBString, Iconv, Recode being available
  226. */
  227. public static function convertCharset($string, $fromCharset, $toCharset='UTF-8') {
  228. if(function_exists('mb_convert_encoding')) return mb_convert_encoding($string, $toCharset, $fromCharset);
  229. if(function_exists('iconv')) return iconv($fromCharset, $toCharset, $string);
  230. if(function_exists('recode_string')) return recode_string($fromCharset .'..'. $toCharset, $string);
  231. throw new PListException('neither iconv nor mbstring supported. how are we supposed to work on strings here?');
  232. }
  233. /**
  234. * Count characters considering character set
  235. * Trying to use MBString, Iconv - in that particular order.
  236. * @param string $string the string to convert
  237. * @param string $charset the charset the given string is currently encoded in
  238. * @return integer The number of characters in that string
  239. * @throws PListException on neither MBString, Iconv being available
  240. */
  241. public static function charsetStrlen($string,$charset="UTF-8") {
  242. if(function_exists('mb_strlen')) return mb_strlen($string, $charset);
  243. if(function_exists('iconv_strlen')) return iconv($string,$charset);
  244. throw new PListException('neither iconv nor mbstring supported. how are we supposed to work on strings here?');
  245. }
  246. /**
  247. * Read a unicode string value, coded as UTF-16BE
  248. * @param string $fname The filename of the file we're reading from
  249. * @param ressource $fd The file descriptor we're reading from
  250. * @param integer $length The length (in bytes) of the string value
  251. * @return CFString The string value, utf8 encoded
  252. * @throws IOException if read error occurs
  253. */
  254. protected function readBinaryUnicodeString($fname,$fd,$length) {
  255. if(strlen($buff = fread($fd, 2*$length)) != 2*$length) throw IOException::readError($fname);
  256. if(!isset($this->uniqueTable[$buff])) $this->uniqueTable[$buf] = true;
  257. return new CFString(self::convertCharset($buff,"UTF-8", "UTF-16BE"));
  258. }
  259. /**
  260. * Read an array value, including contained objects
  261. * @param string $fname The filename of the file we're reading from
  262. * @param ressource $fd The file descriptor we're reading from
  263. * @param integer $length The number of contained objects
  264. * @return CFArray The array value, including the objects
  265. * @throws IOException if read error occurs
  266. */
  267. protected function readBinaryArray($fname,$fd,$length) {
  268. $ary = new CFArray();
  269. // first: read object refs
  270. if($length != 0) {
  271. if(strlen($buff = fread($fd, $length * $this->objectRefSize)) != $length * $this->objectRefSize) throw IOException::readError($fname);
  272. $objects = unpack($this->objectRefSize == 1 ? "C*" : "n*", $buff);
  273. // now: read objects
  274. for($i=0;$i<$length;++$i) {
  275. $object = $this->readBinaryObjectAt($fname,$fd,$objects[$i+1]+1,$this->objectRefSize);
  276. $ary->add($object);
  277. }
  278. }
  279. return $ary;
  280. }
  281. /**
  282. * Read a dictionary value, including contained objects
  283. * @param string $fname The filename of the file we're reading from
  284. * @param ressource $fd The file descriptor we're reading from
  285. * @param integer $length The number of contained objects
  286. * @return CFDictionary The dictionary value, including the objects
  287. * @throws IOException if read error occurs
  288. */
  289. protected function readBinaryDict($fname,$fd,$length) {
  290. $dict = new CFDictionary();
  291. // first: read keys
  292. if($length != 0) {
  293. if(strlen($buff = fread($fd, $length * $this->objectRefSize)) != $length * $this->objectRefSize) throw IOException::readError($fname);
  294. $keys = unpack(($this->objectRefSize == 1 ? "C*" : "n*"), $buff);
  295. // second: read object refs
  296. if(strlen($buff = fread($fd, $length * $this->objectRefSize)) != $length * $this->objectRefSize) throw IOException::readError($fname);
  297. $objects = unpack(($this->objectRefSize == 1 ? "C*" : "n*"), $buff);
  298. // read real keys and objects
  299. for($i=0;$i<$length;++$i) {
  300. $key = $this->readBinaryObjectAt($fname,$fd,$keys[$i+1]+1);
  301. $object = $this->readBinaryObjectAt($fname,$fd,$objects[$i+1]+1);
  302. $dict->add($key->getValue(),$object);
  303. }
  304. }
  305. return $dict;
  306. }
  307. /**
  308. * Read an object type byte, decode it and delegate to the correct reader function
  309. * @param string $fname The filename of the file we're reading from
  310. * @param ressource $fd The file descriptor we're reading from
  311. * @return mixed The value of the delegate reader, so any of the CFType subclasses
  312. * @throws IOException if read error occurs
  313. */
  314. function readBinaryObject($fname,$fd) {
  315. // first: read the marker byte
  316. if(strlen($buff = fread($fd,1)) != 1) throw IOException::readError($fname);
  317. $object_length = unpack("C*", $buff);
  318. $object_length = $object_length[1] & 0xF;
  319. $buff = unpack("H*", $buff);
  320. $buff = $buff[1];
  321. $object_type = substr($buff, 0, 1);
  322. if($object_type != "0" && $object_length == 15) {
  323. $object_length = $this->readBinaryObject($fname,$fd,$this->objectRefSize);
  324. $object_length = $object_length->getValue();
  325. }
  326. $retval = null;
  327. switch($object_type) {
  328. case '0': // null, false, true, fillbyte
  329. $retval = $this->readBinaryNullType($object_length);
  330. break;
  331. case '1': // integer
  332. $retval = $this->readBinaryInt($fname,$fd,$object_length);
  333. break;
  334. case '2': // real
  335. $retval = $this->readBinaryReal($fname,$fd,$object_length);
  336. break;
  337. case '3': // date
  338. $retval = $this->readBinaryDate($fname,$fd,$object_length);
  339. break;
  340. case '4': // data
  341. $retval = $this->readBinaryData($fname,$fd,$object_length);
  342. break;
  343. case '5': // byte string, usually utf8 encoded
  344. $retval = $this->readBinaryString($fname,$fd,$object_length);
  345. break;
  346. case '6': // unicode string (utf16be)
  347. $retval = $this->readBinaryUnicodeString($fname,$fd,$object_length);
  348. break;
  349. case 'a': // array
  350. $retval = $this->readBinaryArray($fname,$fd,$object_length);
  351. break;
  352. case 'd': // dictionary
  353. $retval = $this->readBinaryDict($fname,$fd,$object_length);
  354. break;
  355. }
  356. return $retval;
  357. }
  358. /**
  359. * Read an object type byte at position $pos, decode it and delegate to the correct reader function
  360. * @param string $fname The filename of the file we're reading from
  361. * @param ressource $fd The file descriptor we're reading from
  362. * @param integer $pos The table position in the offsets table
  363. * @return mixed The value of the delegate reader, so any of the CFType subclasses
  364. */
  365. function readBinaryObjectAt($fname,$fd,$pos) {
  366. $position = $this->offsets[$pos];
  367. fseek($fd,$position,SEEK_SET);
  368. return $this->readBinaryObject($fname,$fd);
  369. }
  370. /**
  371. * Read a binary plist file
  372. * @param string $file The file to read
  373. * @return void
  374. * @throws IOException if read error occurs
  375. */
  376. function readBinary($file) {
  377. $this->uniqueTable = Array();
  378. $this->countObjects = 0;
  379. $this->stringSize = 0;
  380. $this->intSize = 0;
  381. $this->miscSize = 0;
  382. $this->objectRefs = 0;
  383. $this->writtenObjectCount = 0;
  384. $this->objectTable = Array();
  385. $this->objectRefSize = 0;
  386. $this->offsets = Array();
  387. $fd = fopen($file,"rb");
  388. // first, we read the trailer: 32 byte from the end
  389. fseek($fd,-32,SEEK_END);
  390. $buff = fread($fd,32);
  391. $infos = unpack("x6/Coffset_size/Cobject_ref_size/x4/Nnumber_of_objects/x4/Ntop_object/x4/Ntable_offset",$buff);
  392. // after that, get the offset table
  393. fseek($fd,$infos['table_offset'], SEEK_SET);
  394. $coded_offset_table = fread($fd,$infos['number_of_objects'] * $infos['offset_size']);
  395. if(strlen($coded_offset_table) != $infos['number_of_objects'] * $infos['offset_size']) throw IOException::readError($file);
  396. $this->countObjects = $infos['number_of_objects'];
  397. // decode offset table
  398. $formats = Array("","C*","n*","(H6)*","N*");
  399. $this->offsets = unpack($formats[$infos['offset_size']],$coded_offset_table);
  400. if($infos['offset_size'] == 3) {
  401. foreach($this->offsets as $k => $v) $this->offsets[$k] = hexdec($v);
  402. }
  403. $this->uniqueTable = Array();
  404. $this->objectRefSize = $infos['object_ref_size'];
  405. $top = $this->readBinaryObjectAt($file,$fd,$infos['top_object']+1);
  406. $this->add($top);
  407. fclose($fd);
  408. }
  409. /**
  410. * calculate the bytes needed for a size integer value
  411. * @param integer $int The integer value to calculate
  412. * @return integer The number of bytes needed
  413. */
  414. public static function bytesSizeInt($int) {
  415. $nbytes = 0;
  416. if($int > 0xE) $nbytes += 2; // 2 size-bytes
  417. if($int > 0xFF) $nbytes += 1; // 3 size-bytes
  418. if($int > 0xFFFF) $nbytes += 2; // 5 size-bytes
  419. return $nbytes;
  420. }
  421. /**
  422. * Calculate the byte needed for a „normal” integer value
  423. * @param integer $int The integer value
  424. * @return integer The number of bytes needed + 1 (because of the „marker byte”)
  425. */
  426. public static function bytesInt($int) {
  427. $nbytes = 1;
  428. if($int > 0xFF) $nbytes += 1; // 2 byte integer
  429. if($int > 0xFFFF) $nbytes += 2; // 4 byte integer
  430. if($int > 0xFFFFFFFF) $nbytes += 4; // 8 byte integer
  431. if($int < 0) $nbytes += 7; // 8 byte integer (since it is signed)
  432. return $nbytes + 1; // one „marker” byte
  433. }
  434. /**
  435. * „pack” a value (i.e. write the binary representation as big endian to a string) with the specified size
  436. * @param integer $nbytes The number of bytes to pack
  437. * @param integer $int the integer value to pack
  438. * @return string The packed value as string
  439. */
  440. public static function packItWithSize($nbytes,$int) {
  441. $formats = Array("C", "n", "N", "N");
  442. $format = $formats[$nbytes-1];
  443. $ret = '';
  444. if($nbytes == 3) return substr(pack($format, $int), -3);
  445. return pack($format, $int);
  446. }
  447. /**
  448. * Calculate the bytes needed to save the number of objects
  449. * @param integer $count_objects The number of objects
  450. * @return integer The number of bytes
  451. */
  452. public static function bytesNeeded($count_objects) {
  453. $nbytes = 0;
  454. while($count_objects >= 1) {
  455. $nbytes++;
  456. $count_objects /= 256;
  457. }
  458. return $nbytes;
  459. }
  460. /**
  461. * Code an integer to byte representation
  462. * @param integer $int The integer value
  463. * @return string The packed byte value
  464. */
  465. public static function intBytes($int) {
  466. $intbytes = "";
  467. if($int > 0xFFFF) $intbytes = "\x12".pack("N", $int); // 4 byte integer
  468. elseif($int > 0xFF) $intbytes = "\x11".pack("n", $int); // 2 byte integer
  469. else $intbytes = "\x10".pack("C", $int); // 8 byte integer
  470. return $intbytes;
  471. }
  472. /**
  473. * Code an type byte, consisting of the type marker and the length of the type
  474. * @param string $type The type byte value (i.e. "d" for dictionaries)
  475. * @param integer $type_len The length of the type
  476. * @return string The packed type byte value
  477. */
  478. public static function typeBytes($type,$type_len) {
  479. $optional_int = "";
  480. if($type_len < 15) $type .= sprintf("%x", $type_len);
  481. else {
  482. $type .= "f";
  483. $optional_int = self::intBytes($type_len);
  484. }
  485. return pack("H*", $type).$optional_int;
  486. }
  487. /**
  488. * Count number of objects and create a unique table for strings
  489. * @param $value The value to count and unique
  490. * @return void
  491. */
  492. protected function uniqueAndCountValues($value) {
  493. // no uniquing for other types than CFString and CFData
  494. if($value instanceof CFNumber) {
  495. $val = $value->getValue();
  496. if(intval($val) == $val && !is_float($val) && strpos($val,'.') === false) $this->intSize += self::bytesInt($val);
  497. else $this->miscSize += 9; // 9 bytes (8 + marker byte) for real
  498. $this->countObjects++;
  499. return;
  500. }
  501. elseif($value instanceof CFDate) {
  502. $this->miscSize += 9; // since date in plist is real, we need 9 byte (8 + marker byte)
  503. $this->countObjects++;
  504. return;
  505. }
  506. elseif($value instanceof CFBoolean) {
  507. $this->countObjects++;
  508. $this->miscSize += 1;
  509. return;
  510. }
  511. elseif($value instanceof CFArray) {
  512. $cnt = 0;
  513. foreach($value as $v) {
  514. ++$cnt;
  515. $this->uniqueAndCountValues($v);
  516. $this->objectRefs++; // each array member is a ref
  517. }
  518. $this->countObjects++;
  519. $this->intSize += self::bytesSizeInt($cnt);
  520. $this->miscSize++; // marker byte for array
  521. return;
  522. }
  523. elseif($value instanceof CFDictionary) {
  524. $cnt = 0;
  525. foreach($value as $k => $v) {
  526. ++$cnt;
  527. if(!isset($this->uniqueTable[$k])) {
  528. $this->uniqueTable[$k] = 0;
  529. $this->stringSize += strlen($k) + 1;
  530. $this->intSize += self::bytesSizeInt(strlen($k));
  531. }
  532. $this->objectRefs += 2; // both, key and value, are refs
  533. $this->uniqueTable[$k]++;
  534. $this->uniqueAndCountValues($v);
  535. }
  536. $this->countObjects++;
  537. $this->miscSize++; // marker byte for dict
  538. $this->intSize += self::bytesSizeInt($cnt);
  539. return;
  540. }
  541. elseif($value instanceOf CFData) {
  542. $val = $value->getValue();
  543. $this->intSize += self::bytesSizeInt(strlen($val));
  544. $this->miscSize += strlen($val) + 1;
  545. $this->countObjects++;
  546. return;
  547. }
  548. else $val = $value->getValue();
  549. if(!isset($this->uniqueTable[$val])) {
  550. $this->uniqueTable[$val] = 0;
  551. $this->stringSize += strlen($val) + 1;
  552. $this->intSize += self::bytesSizeInt(strlen($val));
  553. }
  554. $this->uniqueTable[$val]++;
  555. }
  556. /**
  557. * Convert CFPropertyList to binary format; since we have to count our objects we simply unique CFDictionary and CFArray
  558. * @return string The binary plist content
  559. */
  560. public function toBinary() {
  561. $this->uniqueTable = Array();
  562. $this->countObjects = 0;
  563. $this->stringSize = 0;
  564. $this->intSize = 0;
  565. $this->miscSize = 0;
  566. $this->objectRefs = 0;
  567. $this->writtenObjectCount = 0;
  568. $this->objectTable = Array();
  569. $this->objectRefSize = 0;
  570. $this->offsets = Array();
  571. $binary_str = "bplist00";
  572. $value = $this->getValue();
  573. $this->uniqueAndCountValues($value);
  574. $this->countObjects += count($this->uniqueTable);
  575. $this->objectRefSize = self::bytesNeeded($this->countObjects);
  576. $file_size = $this->stringSize + $this->intSize + $this->miscSize + $this->objectRefs * $this->objectRefSize + 40;
  577. $offset_size = self::bytesNeeded($file_size);
  578. $table_offset = $file_size - 32;
  579. $this->objectTable = Array();
  580. $this->writtenObjectCount = 0;
  581. $this->uniqueTable = Array(); // we needed it to calculate several values
  582. $this->getValue()->toBinary($this);
  583. $object_offset = 8;
  584. $offsets = Array();
  585. for($i=0;$i<count($this->objectTable);++$i) {
  586. $binary_str .= $this->objectTable[$i];
  587. $offsets[$i] = $object_offset;
  588. $object_offset += strlen($this->objectTable[$i]);
  589. }
  590. for($i=0;$i<count($offsets);++$i) {
  591. $binary_str .= self::packItWithSize($offset_size, $offsets[$i]);
  592. }
  593. $binary_str .= pack("x6CC", $offset_size, $this->objectRefSize);
  594. $binary_str .= pack("x4N", $this->countObjects);
  595. $binary_str .= pack("x4N", 0);
  596. $binary_str .= pack("x4N", $table_offset);
  597. return $binary_str;
  598. }
  599. /**
  600. * Uniques and transforms a string value to binary format and adds it to the object table
  601. * @param string $val The string value
  602. * @return integer The position in the object table
  603. */
  604. public function stringToBinary($val) {
  605. $saved_object_count = -1;
  606. if(!isset($this->uniqueTable[$val])) {
  607. $saved_object_count = $this->writtenObjectCount++;
  608. $this->uniqueTable[$val] = $saved_object_count;
  609. $utf16 = false;
  610. for($i=0;$i<strlen($val);++$i) {
  611. if(ord($val{$i}) > 128) {
  612. $utf16 = true;
  613. break;
  614. }
  615. }
  616. if($utf16) {
  617. $bdata = self::typeBytes("6", mb_strlen($val,'UTF-8')); // 6 is 0110, unicode string (utf16be)
  618. $val = self::convertCharset($val, 'UTF-8', 'UTF-16BE');
  619. $this->objectTable[$saved_object_count] = $bdata.$val;
  620. }
  621. else {
  622. $bdata = self::typeBytes("5", strlen($val)); // 5 is 0101 which is an ASCII string (seems to be ASCII encoded)
  623. $this->objectTable[$saved_object_count] = $bdata.$val;
  624. }
  625. }
  626. else $saved_object_count = $this->uniqueTable[$val];
  627. return $saved_object_count;
  628. }
  629. /**
  630. * Codes an integer to binary format
  631. * @param integer $value The integer value
  632. * @return string the coded integer
  633. */
  634. protected function intToBinary($value) {
  635. $nbytes = 0;
  636. if($value > 0xFF) $nbytes = 1; // 1 byte integer
  637. if($value > 0xFFFF) $nbytes += 1; // 4 byte integer
  638. if($value > 0xFFFFFFFF) $nbytes += 1; // 8 byte integer
  639. if($value < 0) $nbytes = 3; // 8 byte integer, since signed
  640. $bdata = self::typeBytes("1", $nbytes); // 1 is 0001, type indicator for integer
  641. $buff = "";
  642. if($nbytes < 3) {
  643. if($nbytes == 0) $fmt = "C";
  644. elseif($nbytes == 1) $fmt = "n";
  645. else $fmt = "N";
  646. $buff = pack($fmt, $value);
  647. }
  648. else {
  649. if(PHP_INT_SIZE > 4) {
  650. // 64 bit signed integer; we need the higher and the lower 32 bit of the value
  651. $high_word = $value >> 32;
  652. $low_word = $value & 0xFFFFFFFF;
  653. }
  654. else {
  655. // since PHP can only handle 32bit signed, we can only get 32bit signed values at this point - values above 0x7FFFFFFF are
  656. // floats. So we ignore the existance of 64bit on non-64bit-machines
  657. if($value < 0) $high_word = 0xFFFFFFFF;
  658. else $high_word = 0;
  659. $low_word = $value;
  660. }
  661. $buff = pack("N", $high_word).pack("N", $low_word);
  662. }
  663. return $bdata.$buff;
  664. }
  665. /**
  666. * Codes a real value to binary format
  667. * @param float $val The real value
  668. * @return string The coded real
  669. */
  670. protected function realToBinary($val) {
  671. $bdata = self::typeBytes("2", 3); // 2 is 0010, type indicator for reals
  672. return $bdata.strrev(pack("d", (float)$val));
  673. }
  674. /**
  675. * Converts a numeric value to binary and adds it to the object table
  676. * @param numeric $value The numeric value
  677. * @return integer The position in the object table
  678. */
  679. public function numToBinary($value) {
  680. $saved_object_count = $this->writtenObjectCount++;
  681. $val = "";
  682. if(intval($value) == $value && !is_float($value) && strpos($value,'.') === false) $val = $this->intToBinary($value);
  683. else $val = $this->realToBinary($value);
  684. $this->objectTable[$saved_object_count] = $val;
  685. return $saved_object_count;
  686. }
  687. /**
  688. * Convert date value (apple format) to binary and adds it to the object table
  689. * @param integer $value The date value
  690. * @return integer The position of the coded value in the object table
  691. */
  692. public function dateToBinary($val) {
  693. $saved_object_count = $this->writtenObjectCount++;
  694. $hour = gmdate("H",$val);
  695. $min = gmdate("i",$val);
  696. $sec = gmdate("s",$val);
  697. $mday = gmdate("j",$val);
  698. $mon = gmdate("n",$val);
  699. $year = gmdate("Y",$val);
  700. $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
  701. $bdata = self::typeBytes("3", 3); // 3 is 0011, type indicator for date
  702. $this->objectTable[$saved_object_count] = $bdata.strrev(pack("d", $val));
  703. return $saved_object_count;
  704. }
  705. /**
  706. * Convert a bool value to binary and add it to the object table
  707. * @param bool $val The boolean value
  708. * @return integer The position in the object table
  709. */
  710. public function boolToBinary($val) {
  711. $saved_object_count = $this->writtenObjectCount++;
  712. $this->objectTable[$saved_object_count] = $val ? "\x9" : "\x8"; // 0x9 is 1001, type indicator for true; 0x8 is 1000, type indicator for false
  713. return $saved_object_count;
  714. }
  715. /**
  716. * Convert data value to binary format and add it to the object table
  717. * @param string $val The data value
  718. * @return integer The position in the object table
  719. */
  720. public function dataToBinary($val) {
  721. $saved_object_count = $this->writtenObjectCount++;
  722. $bdata = self::typeBytes("4", strlen($val)); // a is 1000, type indicator for data
  723. $this->objectTable[$saved_object_count] = $bdata.$val;
  724. return $saved_object_count;
  725. }
  726. /**
  727. * Convert array to binary format and add it to the object table
  728. * @param CFArray $val The array to convert
  729. * @return integer The position in the object table
  730. */
  731. public function arrayToBinary($val) {
  732. $saved_object_count = $this->writtenObjectCount++;
  733. $bdata = self::typeBytes("a", count($val->getValue())); // a is 1010, type indicator for arrays
  734. foreach($val as $v) {
  735. $bval = $v->toBinary($this);
  736. $bdata .= self::packItWithSize($this->objectRefSize, $bval);
  737. }
  738. $this->objectTable[$saved_object_count] = $bdata;
  739. return $saved_object_count;
  740. }
  741. /**
  742. * Convert dictionary to binary format and add it to the object table
  743. * @param CFDictionary $val The dict to convert
  744. * @return integer The position in the object table
  745. */
  746. public function dictToBinary($val) {
  747. $saved_object_count = $this->writtenObjectCount++;
  748. $bdata = self::typeBytes("d", count($val->getValue())); // d=1101, type indicator for dictionary
  749. foreach($val as $k => $v) {
  750. $str = new CFString($k);
  751. $key = $str->toBinary($this);
  752. $bdata .= self::packItWithSize($this->objectRefSize, $key);
  753. }
  754. foreach($val as $k => $v) {
  755. $bval = $v->toBinary($this);
  756. $bdata .= self::packItWithSize($this->objectRefSize, $bval);
  757. }
  758. $this->objectTable[$saved_object_count] = $bdata;
  759. return $saved_object_count;
  760. }
  761. }
  762. ?>