Deserializer.php 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841
  1. <?php
  2. /**
  3. * This file is part of amfPHP
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the license that is bundled
  8. * with this package in the file license.txt.
  9. */
  10. /**
  11. * Amfphp_Core_Amf_Deserializer takes the raw amf input stream and converts it PHP objects
  12. * representing the data.
  13. *
  14. * Based on code from amfphp 1.9, adapted and enhanced by Ariel Sommeria-Klein to amfphp 2.x architecture
  15. * Vector code by Mick Powell
  16. * @package Amfphp_Core_Amf
  17. */
  18. class Amfphp_Core_Amf_Deserializer implements Amfphp_Core_Common_IDeserializer {
  19. /**
  20. * data to deserialize
  21. * @var string
  22. */
  23. protected $rawData;
  24. /**
  25. * The number of Messages in the packet left to process
  26. *
  27. * @access protected
  28. * @var int
  29. */
  30. protected $messagesLeftToProcess;
  31. /**
  32. * The current seek cursor of the stream
  33. *
  34. * @access protected
  35. * @var int
  36. */
  37. protected $currentByte;
  38. /**
  39. * The number of headers in the packet left to process
  40. *
  41. * @access protected
  42. * @var int
  43. */
  44. protected $headersLeftToProcess;
  45. /**
  46. * the Packet contained in the serialized data
  47. * @var <Amfphp_Core_Amf_Packet>
  48. */
  49. protected $deserializedPacket;
  50. /**
  51. * strings stored for tracking references(amf3)
  52. * @var array
  53. */
  54. protected $storedStrings;
  55. /**
  56. * objects stored for tracking references(amf3)
  57. * @var array
  58. */
  59. protected $storedObjects;
  60. /**
  61. * class definitions(traits) stored for tracking references(amf3)
  62. * @var array
  63. */
  64. protected $storedDefinitions;
  65. /**
  66. * objects stored for tracking references(amf0)
  67. * @var array
  68. */
  69. protected $amf0storedObjects;
  70. /**
  71. * converts VOs directly if set, rather than instanciating anonymous classes that are converted later
  72. * @var Amfphp_Core_Common_IVoConverter
  73. */
  74. public $voConverter;
  75. /**
  76. * convert from text/binary to php object
  77. * @param array $getData
  78. * @param array $postData
  79. * @param string $rawPostData
  80. * @return Amfphp_Core_Amf_Packet
  81. */
  82. public function deserialize(array $getData, array $postData, $rawPostData) {
  83. $this->rawData = $rawPostData;
  84. $this->currentByte = 0;
  85. $this->deserializedPacket = new Amfphp_Core_Amf_Packet();
  86. $this->readHeaders(); // read the binary headers
  87. $this->readMessages(); // read the binary Messages
  88. return $this->deserializedPacket;
  89. }
  90. /**
  91. * reset reference stores
  92. */
  93. protected function resetReferences() {
  94. $this->amf0storedObjects = array();
  95. $this->storedStrings = array();
  96. $this->storedObjects = array();
  97. $this->storedDefinitions = array();
  98. }
  99. /**
  100. * readHeaders converts that header section of the amf Packet into php obects.
  101. * Header information typically contains meta data about the Packet.
  102. */
  103. protected function readHeaders() {
  104. $topByte = $this->readByte(); // ignore the first two bytes -- version or something
  105. $secondByte = $this->readByte(); //0 for Flash,
  106. //If firstByte != 0, then the Amf data is corrupted, for example the transmission
  107. //
  108. if (!($topByte == 0 || $topByte == 3)) {
  109. throw new Amfphp_Core_Exception('Malformed Amf Packet, connection may have dropped');
  110. }
  111. if ($secondByte == 3) {
  112. $this->deserializedPacket->amfVersion = Amfphp_Core_Amf_Constants::AMF3_ENCODING;
  113. }
  114. $this->headersLeftToProcess = $this->readInt(); // find the total number of header elements
  115. while ($this->headersLeftToProcess--) { // loop over all of the header elements
  116. $this->resetReferences();
  117. $name = $this->readUTF();
  118. $required = $this->readByte() == 1; // find the must understand flag
  119. //$length = $this->readLong(); // grab the length of the header element
  120. $this->currentByte += 4; // grab the length of the header element
  121. $type = $this->readByte(); // grab the type of the element
  122. $content = $this->readData($type); // turn the element into real data
  123. $header = new Amfphp_Core_Amf_Header($name, $required, $content);
  124. $this->deserializedPacket->headers[] = $header;
  125. }
  126. }
  127. /**
  128. * read messages in AMF packet
  129. */
  130. protected function readMessages() {
  131. $this->messagesLeftToProcess = $this->readInt(); // find the total number of Message elements
  132. while ($this->messagesLeftToProcess--) { // loop over all of the Message elements
  133. $this->resetReferences();
  134. $target = $this->readUTF();
  135. $response = $this->readUTF(); // the response that the client understands
  136. //$length = $this->readLong(); // grab the length of the Message element
  137. $this->currentByte += 4;
  138. $type = $this->readByte(); // grab the type of the element
  139. $data = $this->readData($type); // turn the element into real data
  140. $message = new Amfphp_Core_Amf_Message($target, $response, $data);
  141. $this->deserializedPacket->messages[] = $message;
  142. }
  143. }
  144. /**
  145. * readInt grabs the next 2 bytes and returns the next two bytes, shifted and combined
  146. * to produce the resulting integer
  147. *
  148. * @return int The resulting integer from the next 2 bytes
  149. */
  150. protected function readInt() {
  151. return ((ord($this->rawData[$this->currentByte++]) << 8) |
  152. ord($this->rawData[$this->currentByte++])); // read the next 2 bytes, shift and add
  153. }
  154. /**
  155. * readUTF first grabs the next 2 bytes which represent the string length.
  156. * Then it grabs the next (len) bytes of the resulting string.
  157. *
  158. * @return string The utf8 decoded string
  159. */
  160. protected function readUTF() {
  161. $length = $this->readInt(); // get the length of the string (1st 2 bytes)
  162. //BUg fix:: if string is empty skip ahead
  163. if ($length == 0) {
  164. return '';
  165. } else {
  166. $val = substr($this->rawData, $this->currentByte, $length); // grab the string
  167. $this->currentByte += $length; // move the seek head to the end of the string
  168. return $val; // return the string
  169. }
  170. }
  171. /**
  172. * readByte grabs the next byte from the data stream and returns it.
  173. *
  174. * @return int The next byte converted into an integer
  175. */
  176. protected function readByte() {
  177. return ord($this->rawData[$this->currentByte++]); // return the next byte
  178. }
  179. /**
  180. * readData is the main switch for mapping a type code to an actual
  181. * implementation for deciphering it.
  182. *
  183. * @param mixed $type The $type integer
  184. * @return mixed The php version of the data in the Packet block
  185. */
  186. public function readData($type) {
  187. switch ($type) {
  188. //amf3 is now most common, so start with that
  189. case 0x11: //Amf3-specific
  190. return $this->readAmf3Data();
  191. break;
  192. case 0: // number
  193. return $this->readDouble();
  194. case 1: // boolean
  195. return $this->readByte() == 1;
  196. case 2: // string
  197. return $this->readUTF();
  198. case 3: // object Object
  199. return $this->readObject();
  200. //ignore movie clip
  201. case 5: // null
  202. return null;
  203. case 6: // undefined
  204. return new Amfphp_Core_Amf_Types_Undefined();
  205. case 7: // Circular references are returned here
  206. return $this->readReference();
  207. case 8: // mixed array with numeric and string keys
  208. return $this->readMixedArray();
  209. case 9: //object end. not worth , TODO maybe some integrity checking
  210. return null;
  211. case 0X0A: // array
  212. return $this->readArray();
  213. case 0X0B: // date
  214. return $this->readDate();
  215. case 0X0C: // string, strlen(string) > 2^16
  216. return $this->readLongUTF();
  217. case 0X0D: // mainly internal AS objects
  218. return null;
  219. //ignore recordset
  220. case 0X0F: // XML
  221. return $this->readXml();
  222. case 0x10: // Custom Class
  223. return $this->readCustomClass();
  224. default: // unknown case
  225. throw new Amfphp_Core_Exception("Found unhandled type with code: $type");
  226. exit();
  227. break;
  228. }
  229. return $data;
  230. }
  231. /**
  232. * readDouble reads the floating point value from the bytes stream and properly orders
  233. * the bytes depending on the system architecture.
  234. *
  235. * @return float The floating point value of the next 8 bytes
  236. */
  237. protected function readDouble() {
  238. $bytes = substr($this->rawData, $this->currentByte, 8);
  239. $this->currentByte += 8;
  240. if (Amfphp_Core_Amf_Util::isSystemBigEndian()) {
  241. $bytes = strrev($bytes);
  242. }
  243. $zz = unpack('dflt', $bytes); // unpack the bytes
  244. return $zz['flt']; // return the number from the associative array
  245. }
  246. /**
  247. * readObject reads the name/value properties of the amf Packet and converts them into
  248. * their equivilent php representation
  249. *
  250. * @return Object The php object filled with the data
  251. */
  252. protected function readObject() {
  253. $ret = new stdClass();
  254. $this->amf0storedObjects[] = & $ret;
  255. $key = $this->readUTF();
  256. for ($type = $this->readByte(); $type != 9; $type = $this->readByte()) {
  257. $val = $this->readData($type); // grab the value
  258. $ret->$key = $val; // save the name/value pair in the object
  259. $key = $this->readUTF(); // get the next name
  260. }
  261. return $ret;
  262. }
  263. /**
  264. * readReference replaces the old readFlushedSO. It treats where there
  265. * are references to other objects. Currently it does not resolve the
  266. * object as this would involve a serious amount of overhead, unless
  267. * you have a genius idea
  268. *
  269. * @return String
  270. */
  271. protected function readReference() {
  272. $reference = $this->readInt();
  273. return $this->amf0storedObjects[$reference];
  274. }
  275. /**
  276. * readMixedArray turns an array with numeric and string indexes into a php array
  277. *
  278. * @return array The php array with mixed indexes
  279. */
  280. protected function readMixedArray() {
  281. //$length = $this->readLong(); // get the length property set by flash
  282. $this->currentByte += 4;
  283. return $this->readMixedObject(); // return the Message of mixed array
  284. }
  285. /**
  286. * readMixedObject reads the name/value properties of the amf Packet and converts
  287. * numeric looking keys to numeric keys
  288. *
  289. * @return array The php array with the object data
  290. */
  291. protected function readMixedObject() {
  292. $ret = array(); // init the array
  293. $this->amf0storedObjects[] = & $ret;
  294. $key = $this->readUTF(); // grab the key
  295. for ($type = $this->readByte(); $type != 9; $type = $this->readByte()) {
  296. $val = $this->readData($type); // grab the value
  297. if (is_numeric($key)) {
  298. $key = (float) $key;
  299. }
  300. $ret[$key] = $val; // save the name/value pair in the array
  301. $key = $this->readUTF(); // get the next name
  302. }
  303. return $ret; // return the array
  304. }
  305. /**
  306. * readArray turns an all numeric keyed actionscript array into a php array.
  307. *
  308. * @return array The php array
  309. */
  310. protected function readArray() {
  311. $ret = array(); // init the array object
  312. $this->amf0storedObjects[] = & $ret;
  313. $length = $this->readLong(); // get the length of the array
  314. for ($i = 0; $i < $length; $i++) { // loop over all of the elements in the data
  315. $type = $this->readByte(); // grab the type for each element
  316. $ret[] = $this->readData($type); // grab each element
  317. }
  318. return $ret; // return the data
  319. }
  320. /**
  321. * readDate reads a date from the amf Packet and returns the time in ms.
  322. * This method is still under development.
  323. *
  324. * @return Amfphp_Core_Amf_Types_Date a container with the date in ms.
  325. */
  326. protected function readDate() {
  327. $ms = $this->readDouble(); // date in milliseconds from 01/01/1970
  328. $int = $this->readInt(); // unsupported timezone
  329. $date = new Amfphp_Core_Amf_Types_Date($ms);
  330. return $date;
  331. }
  332. /**
  333. * read xml
  334. * @return Amfphp_Core_Amf_Types_Xml
  335. */
  336. protected function readXml() {
  337. $str = $this->readLongUTF();
  338. return new Amfphp_Core_Amf_Types_Xml($str);
  339. }
  340. /**
  341. * readLongUTF first grabs the next 4 bytes which represent the string length.
  342. * Then it grabs the next (len) bytes of the resulting in the string
  343. *
  344. * @return string The utf8 decoded string
  345. */
  346. protected function readLongUTF() {
  347. $length = $this->readLong(); // get the length of the string (1st 4 bytes)
  348. $val = substr($this->rawData, $this->currentByte, $length); // grab the string
  349. $this->currentByte += $length; // move the seek head to the end of the string
  350. return $val; // return the string
  351. }
  352. /**
  353. * tries to use the type to get a typed object. If not possible, return a stdClass,
  354. * with the explicit type marker set if the type was not just an empty string
  355. * @param type $typeIdentifier
  356. * @return stdClass or typed object
  357. */
  358. protected function resolveType($typeIdentifier) {
  359. if ($typeIdentifier != '') {
  360. if ($this->voConverter) {
  361. $obj = $this->voConverter->getNewVoInstance($typeIdentifier);
  362. } else {
  363. $obj = new stdClass();
  364. $explicitTypeField = Amfphp_Core_Amf_Constants::FIELD_EXPLICIT_TYPE;
  365. $obj->$explicitTypeField = $typeIdentifier;
  366. }
  367. } else {
  368. $obj = new stdClass();
  369. }
  370. return $obj;
  371. }
  372. /**
  373. * readCustomClass reads the amf content associated with a class instance which was registered
  374. * with Object.registerClass.
  375. * If a VoConverter is available, it is used to instanciate the Vo.
  376. * If not, In order to preserve the class name an additional property is assigned
  377. * to the object Amfphp_Core_Amf_Constants::FIELD_EXPLICIT_TYPE. This property will be overwritten if it existed within the class already.
  378. *
  379. * @return object The php representation of the object
  380. */
  381. protected function readCustomClass() {
  382. //not really sure why the replace is here? A.S. 201310
  383. $typeIdentifier = str_replace('..', '', $this->readUTF());
  384. $obj = $this->resolveType($typeIdentifier);
  385. $this->amf0storedObjects[] = & $obj;
  386. $key = $this->readUTF(); // grab the key
  387. for ($type = $this->readByte(); $type != 9; $type = $this->readByte()) {
  388. $val = $this->readData($type); // grab the value
  389. $obj->$key = $val; // save the name/value pair in the array
  390. $key = $this->readUTF(); // get the next name
  391. }
  392. return $obj;
  393. }
  394. /**
  395. * read the type byte, then call the corresponding amf3 data reading function
  396. * @return mixed
  397. */
  398. public function readAmf3Data() {
  399. $type = $this->readByte();
  400. switch ($type) {
  401. case 0x00 :
  402. return new Amfphp_Core_Amf_Types_Undefined();
  403. case 0x01 :
  404. return null; //null
  405. case 0x02 :
  406. return false; //boolean false
  407. case 0x03 :
  408. return true; //boolean true
  409. case 0x04 :
  410. return $this->readAmf3Int();
  411. case 0x05 :
  412. return $this->readDouble();
  413. case 0x06 :
  414. return $this->readAmf3String();
  415. case 0x07 :
  416. return $this->readAmf3XmlDocument();
  417. case 0x08 :
  418. return $this->readAmf3Date();
  419. case 0x09 :
  420. return $this->readAmf3Array();
  421. case 0x0A :
  422. return $this->readAmf3Object();
  423. case 0x0B :
  424. return $this->readAmf3Xml();
  425. case 0x0C :
  426. return $this->readAmf3ByteArray();
  427. case 0x0D :
  428. case 0x0E :
  429. case 0x0F :
  430. case 0x10 :
  431. return $this->readAmf3Vector($type);
  432. case 0x11 :
  433. throw new Amfphp_Core_Exception('dictionaries not supported, as it is not possible to use an object as array key in PHP ');
  434. default:
  435. throw new Amfphp_Core_Exception('undefined Amf3 type encountered: ' . $type);
  436. }
  437. }
  438. /**
  439. * Handle decoding of the variable-length representation
  440. * which gives seven bits of value per serialized byte by using the high-order bit
  441. * of each byte as a continuation flag.
  442. *
  443. * @return read integer value
  444. */
  445. protected function readAmf3Int() {
  446. $int = $this->readByte();
  447. if ($int < 128)
  448. return $int;
  449. else {
  450. $int = ($int & 0x7f) << 7;
  451. $tmp = $this->readByte();
  452. if ($tmp < 128) {
  453. return $int | $tmp;
  454. } else {
  455. $int = ($int | ($tmp & 0x7f)) << 7;
  456. $tmp = $this->readByte();
  457. if ($tmp < 128) {
  458. return $int | $tmp;
  459. } else {
  460. $int = ($int | ($tmp & 0x7f)) << 8;
  461. $tmp = $this->readByte();
  462. $int |= $tmp;
  463. // Integers in Amf3 are 29 bit. The MSB (of those 29 bit) is the sign bit.
  464. // In order to properly convert that integer to a PHP integer - the system
  465. // might be 32 bit, 64 bit, 128 bit or whatever - all higher bits need to
  466. // be set.
  467. if (($int & 0x10000000) !== 0) {
  468. $int |= ~0x1fffffff; // extend the sign bit regardless of integer (bit) size
  469. }
  470. return $int;
  471. }
  472. }
  473. }
  474. }
  475. /**
  476. * read amf 3 date
  477. * @return boolean|\Amfphp_Core_Amf_Types_Date
  478. * @throws Amfphp_Core_Exception
  479. */
  480. protected function readAmf3Date() {
  481. $firstInt = $this->readAmf3Int();
  482. if (($firstInt & 0x01) == 0) {
  483. $firstInt = $firstInt >> 1;
  484. if ($firstInt >= count($this->storedObjects)) {
  485. throw new Amfphp_Core_Exception('Undefined date reference: ' . $firstInt);
  486. return false;
  487. }
  488. return $this->storedObjects[$firstInt];
  489. }
  490. $ms = $this->readDouble();
  491. $date = new Amfphp_Core_Amf_Types_Date($ms);
  492. $this->storedObjects[] = & $date;
  493. return $date;
  494. }
  495. /**
  496. * readString
  497. *
  498. * @return string
  499. */
  500. protected function readAmf3String() {
  501. $strref = $this->readAmf3Int();
  502. if (($strref & 0x01) == 0) {
  503. $strref = $strref >> 1;
  504. if ($strref >= count($this->storedStrings)) {
  505. throw new Amfphp_Core_Exception('Undefined string reference: ' . $strref, E_USER_ERROR);
  506. return false;
  507. }
  508. return $this->storedStrings[$strref];
  509. } else {
  510. $strlen = $strref >> 1;
  511. $str = '';
  512. if ($strlen > 0) {
  513. $str = $this->readBuffer($strlen);
  514. $this->storedStrings[] = $str;
  515. }
  516. return $str;
  517. }
  518. }
  519. /**
  520. * read amf 3 xml
  521. * @return Amfphp_Core_Amf_Types_Xml
  522. */
  523. protected function readAmf3Xml() {
  524. $handle = $this->readAmf3Int();
  525. $inline = (($handle & 1) != 0);
  526. $handle = $handle >> 1;
  527. if ($inline) {
  528. $xml = $this->readBuffer($handle);
  529. $this->storedObjects[] = & $xml;
  530. } else {
  531. $xml = $this->storedObjects[$handle];
  532. }
  533. return new Amfphp_Core_Amf_Types_Xml($xml);
  534. }
  535. /**
  536. * read amf 3 xml doc
  537. * @return Amfphp_Core_Amf_Types_Xml
  538. */
  539. protected function readAmf3XmlDocument() {
  540. $handle = $this->readAmf3Int();
  541. $inline = (($handle & 1) != 0);
  542. $handle = $handle >> 1;
  543. if ($inline) {
  544. $xml = $this->readBuffer($handle);
  545. $this->storedObjects[] = & $xml;
  546. } else {
  547. $xml = $this->storedObjects[$handle];
  548. }
  549. return new Amfphp_Core_Amf_Types_XmlDocument($xml);
  550. }
  551. /**
  552. * read Amf 3 byte array
  553. * @return Amfphp_Core_Amf_Types_ByteArray
  554. */
  555. protected function readAmf3ByteArray() {
  556. $handle = $this->readAmf3Int();
  557. $inline = (($handle & 1) != 0);
  558. $handle = $handle >> 1;
  559. if ($inline) {
  560. $ba = new Amfphp_Core_Amf_Types_ByteArray($this->readBuffer($handle));
  561. $this->storedObjects[] = & $ba;
  562. } else {
  563. $ba = $this->storedObjects[$handle];
  564. }
  565. return $ba;
  566. }
  567. /**
  568. * read amf 3 array
  569. * @return array
  570. */
  571. protected function readAmf3Array() {
  572. $handle = $this->readAmf3Int();
  573. $inline = (($handle & 1) != 0);
  574. $handle = $handle >> 1;
  575. if ($inline) {
  576. $hashtable = array();
  577. $this->storedObjects[] = & $hashtable;
  578. $key = $this->readAmf3String();
  579. while ($key != '') {
  580. $value = $this->readAmf3Data();
  581. $hashtable[$key] = $value;
  582. $key = $this->readAmf3String();
  583. }
  584. for ($i = 0; $i < $handle; $i++) {
  585. //Grab the type for each element.
  586. $value = $this->readAmf3Data();
  587. $hashtable[$i] = $value;
  588. }
  589. return $hashtable;
  590. } else {
  591. return $this->storedObjects[$handle];
  592. }
  593. }
  594. /**
  595. * read amf 3 object
  596. * @return mixed stdClass, or VoClass if VoConverter could find it.
  597. */
  598. protected function readAmf3Object() {
  599. $handle = $this->readAmf3Int();
  600. $inline = (($handle & 1) != 0);
  601. $handle = $handle >> 1;
  602. if ($inline) {
  603. //an inline object
  604. $inlineClassDef = (($handle & 1) != 0);
  605. $handle = $handle >> 1;
  606. if ($inlineClassDef) {
  607. //inline class-def
  608. $typeIdentifier = $this->readAmf3String();
  609. $typedObject = !is_null($typeIdentifier) && $typeIdentifier != '';
  610. //flags that identify the way the object is serialized/deserialized
  611. $externalizable = (($handle & 1) != 0);
  612. $handle = $handle >> 1;
  613. $dynamic = (($handle & 1) != 0);
  614. $handle = $handle >> 1;
  615. $classMemberCount = $handle;
  616. $classMemberDefinitions = array();
  617. for ($i = 0; $i < $classMemberCount; $i++) {
  618. $classMemberDefinitions[] = $this->readAmf3String();
  619. }
  620. $classDefinition = array('type' => $typeIdentifier, 'members' => $classMemberDefinitions,
  621. 'externalizable' => $externalizable, 'dynamic' => $dynamic);
  622. $this->storedDefinitions[] = $classDefinition;
  623. } else {
  624. //a reference to a previously passed class-def
  625. $classDefinition = $this->storedDefinitions[$handle];
  626. }
  627. } else {
  628. //an object reference
  629. return $this->storedObjects[$handle];
  630. }
  631. $typeIdentifier = $classDefinition['type'];
  632. $obj = $this->resolveType($typeIdentifier);
  633. //Add to references as circular references may search for this object
  634. $this->storedObjects[] = & $obj;
  635. if ($classDefinition['externalizable']) {
  636. if (($typeIdentifier == 'flex.messaging.io.ArrayCollection') || ($typeIdentifier == 'flex.messaging.io.ObjectProxy')) {
  637. //special for Flex. This doesn't belong here, but it's the least worst way I found to support returning them
  638. $obj = $this->readAmf3Data();
  639. } else {
  640. $externalizedDataField = Amfphp_Core_Amf_Constants::FIELD_EXTERNALIZED_DATA;
  641. $obj->$externalizedDataField = $this->readAmf3Data();
  642. }
  643. } else {
  644. $members = $classDefinition['members'];
  645. $memberCount = count($members);
  646. for ($i = 0; $i < $memberCount; $i++) {
  647. $val = $this->readAmf3Data();
  648. $key = $members[$i];
  649. $obj->$key = $val;
  650. }
  651. if ($classDefinition['dynamic'] /* && obj is ASObject */) {
  652. $key = $this->readAmf3String();
  653. while ($key != '') {
  654. $value = $this->readAmf3Data();
  655. $obj->$key = $value;
  656. $key = $this->readAmf3String();
  657. }
  658. }
  659. }
  660. return $obj;
  661. }
  662. /**
  663. * readLong grabs the next 4 bytes shifts and combines them to produce an integer
  664. *
  665. * @return int The resulting integer from the next 4 bytes
  666. */
  667. protected function readLong() {
  668. return ((ord($this->rawData[$this->currentByte++]) << 24) |
  669. (ord($this->rawData[$this->currentByte++]) << 16) |
  670. (ord($this->rawData[$this->currentByte++]) << 8) |
  671. ord($this->rawData[$this->currentByte++])); // read the next 4 bytes, shift and add
  672. }
  673. /**
  674. * read some data and move pointer
  675. * @param type $len
  676. * @return mixed
  677. */
  678. protected function readBuffer($len) {
  679. $data = '';
  680. for ($i = 0; $i < $len; $i++) {
  681. $data .= $this->rawData
  682. {$i + $this->currentByte};
  683. }
  684. $this->currentByte += $len;
  685. return $data;
  686. }
  687. /**
  688. * Reads a vector array of objects from the AMF stream. This works for all vector arrays: vector-object, vector-int vector-uint and
  689. * vector-double. The Vector is cast to a PHP array. Please note that because of the way php handles integers, uints have to be cast as
  690. * floats. See {@link http://php.net/manual/en/language.types.integer.php}
  691. * @param int Type - the AMF vector array type.
  692. * @return array The objects in the vector in a native PHP array.
  693. */
  694. protected function readAmf3Vector($type) {
  695. /* AMF Spec: "The first (low) bit is a flag with value 1. The remaining 1 to 28 significant bits are used to encode the count of
  696. items in Vector." */
  697. // according to the above - $inline will always be 1 after the bitshift, and what remains in $handle
  698. // after the bitshift is the count of the vector
  699. $handle = $this->readAmf3Int();
  700. $inline = (($handle & 1) != 0 );
  701. $handle = $handle >> 1;
  702. if ($inline) {
  703. $vector = new Amfphp_Core_Amf_Types_Vector();
  704. $vector->type = $type;
  705. /* AMF Spec: "Boolean U8 value, 0x00 if not a fixed-length Vector, otherwise 0x01 if fixed-length." */
  706. // we are not really concerned in PHP if the vector is fixed-length right now.
  707. $vector->fixed = $this->readByte();
  708. $vector->data = array();
  709. $this->storedObjects[] = & $vector;
  710. if ($type === Amfphp_Core_Amf_Types_Vector::VECTOR_OBJECT) {
  711. $vector->className = $this->readAmf3String();
  712. for ($i = 0; $i < $handle; $i++) {
  713. //Grab the type for each element.
  714. $vector->data[] = $this->readAmf3Data();
  715. }
  716. }else{
  717. switch ($type) {
  718. case Amfphp_Core_Amf_Types_Vector::VECTOR_INT : ;
  719. $length = 4;
  720. $format = "ival";
  721. break;
  722. case Amfphp_Core_Amf_Types_Vector::VECTOR_UINT :;
  723. $length = 4;
  724. $format = "Ival";
  725. break;
  726. case Amfphp_Core_Amf_Types_Vector::VECTOR_DOUBLE : ;
  727. $length = 8;
  728. $format = "dval";
  729. break;
  730. }
  731. for ($i = 0; $i < $handle; $i++) {
  732. //Grab the type for each element.
  733. $vector->data[] = $this->readAmf3VectorValue($length, $format);
  734. }
  735. }
  736. return $vector;
  737. } else {
  738. return $this->storedObjects[$handle];
  739. }
  740. }
  741. /**
  742. * Read numeric values from the AMF byte stream. Please be aware that unsigned integers are not really supported in PHP, and for this reason
  743. * unsigned integers are cast to float. {@link http://php.net/manual/en/language.types.integer.php}.
  744. *
  745. * @param integer You can specify 4 for integers or 8 for double precision floating point.
  746. * @param string 'ival' for signed integers, 'Ival' for unsigned integers, and "dval" for double precision floating point
  747. *
  748. * @return <type>
  749. */
  750. protected function readAmf3VectorValue($length, $format) {
  751. $bytes = $this->readBuffer($length);
  752. if (Amfphp_Core_Amf_Util::isSystemBigEndian()) {
  753. $bytes = strrev($bytes);
  754. }
  755. $array = unpack($format, $bytes);
  756. // Unsigned Integers don't work in PHP amazingly enough. If you go into the "upper" region
  757. // on the Actionscript side, this will come through as a negative without this cast to a float
  758. // see http://php.net/manual/en/language.types.integer.php
  759. if ($format === "Ival") {
  760. $array["val"] = floatval(sprintf('%u', $array["val"]));
  761. }
  762. return $array["val"];
  763. }
  764. }
  765. ?>