AMFDeserializer.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. <?php
  2. /**
  3. * AMFDeserializer takes the raw amf input stream and converts it PHP objects
  4. * representing the data.
  5. *
  6. * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  7. * @copyright (c) 2003 amfphp.org
  8. * @package flashservices
  9. * @subpackage io
  10. * @version $Id$
  11. */
  12. include_once(AMFPHP_BASE . "amf/io/AMFBaseDeserializer.php");
  13. class AMFDeserializer extends AMFBaseDeserializer {
  14. /**
  15. * The raw data input
  16. *
  17. * @access private
  18. * @var string
  19. */
  20. var $raw_data;
  21. /**
  22. * The current seek cursor of the stream
  23. *
  24. * @access private
  25. * @var int
  26. */
  27. var $current_byte;
  28. /**
  29. * The length of the stream. Since this class is not actually using a stream
  30. * the entire content of the stream is passed in as the initial argument so the
  31. * length can be determined.
  32. *
  33. * @access private
  34. * @var int
  35. */
  36. var $content_length;
  37. /**
  38. * The number of headers in the packet.
  39. *
  40. * @access private
  41. * @var int
  42. */
  43. var $header_count;
  44. /**
  45. * The content of the packet headers
  46. *
  47. * @access private
  48. * @var string
  49. */
  50. var $headers;
  51. /**
  52. * The number of bodys in the packet.
  53. *
  54. * @access private
  55. * @var int
  56. */
  57. var $body_count;
  58. /**
  59. * The content of the body elements
  60. *
  61. * @access private
  62. * @var string
  63. */
  64. var $body;
  65. /**
  66. * The object to store the amf data.
  67. *
  68. * @access private
  69. * @var object
  70. */
  71. var $amfdata;
  72. /**
  73. * The instance of the amfinput stream object
  74. *
  75. * @access private
  76. * @var object
  77. */
  78. var $inputStream;
  79. /**
  80. * metaInfo
  81. */
  82. var $meta;
  83. var $storedStrings;
  84. var $storedObjects;
  85. var $storedDefinitions;
  86. var $amf0storedObjects;
  87. var $native;
  88. /**
  89. * Constructor method for the deserializer. Constructing the deserializer converts the input stream
  90. * content to a AMFObject.
  91. *
  92. * @param object $is The referenced input stream
  93. */
  94. function AMFDeserializer($rd) {
  95. AMFBaseDeserializer::AMFBaseDeserializer($rd);
  96. }
  97. /**
  98. * readObject reads the name/value properties of the amf message and converts them into
  99. * their equivilent php representation
  100. *
  101. * @return array The php array with the object data
  102. */
  103. function readObject() {
  104. $ret = array(); // init the array
  105. $this->amf0storedObjects[] = & $ret;
  106. $key = $this->readUTF(); // grab the key
  107. for ($type = $this->readByte(); $type != 9; $type = $this->readByte()) {
  108. $val = $this->readData($type); // grab the value
  109. $ret[$key] = $val; // save the name/value pair in the array
  110. $key = $this->readUTF(); // get the next name
  111. }
  112. return $ret; // return the array
  113. }
  114. /**
  115. * readMixedObject reads the name/value properties of the amf message and converts
  116. * numeric looking keys to numeric keys
  117. *
  118. * @return array The php array with the object data
  119. */
  120. function readMixedObject() {
  121. $ret = array(); // init the array
  122. $this->amf0storedObjects[] = & $ret;
  123. $key = $this->readUTF(); // grab the key
  124. for ($type = $this->readByte(); $type != 9; $type = $this->readByte()) {
  125. $val = $this->readData($type); // grab the value
  126. if(is_numeric($key))
  127. {
  128. $key = (float) $key;
  129. }
  130. $ret[$key] = $val; // save the name/value pair in the array
  131. $key = $this->readUTF(); // get the next name
  132. }
  133. return $ret; // return the array
  134. }
  135. /**
  136. * readArray turns an all numeric keyed actionscript array into a php array.
  137. *
  138. * @return array The php array
  139. */
  140. function readArray() {
  141. $ret = array(); // init the array object
  142. $this->amf0storedObjects[] = & $ret;
  143. $length = $this->readLong(); // get the length of the array
  144. for ($i = 0; $i < $length; $i++) { // loop over all of the elements in the data
  145. $type = $this->readByte(); // grab the type for each element
  146. $ret[] = $this->readData($type); // grab each element
  147. }
  148. return $ret; // return the data
  149. }
  150. /**
  151. * readMixedArray turns an array with numeric and string indexes into a php array
  152. *
  153. * @return array The php array with mixed indexes
  154. */
  155. function readMixedArray() {
  156. //$length = $this->readLong(); // get the length property set by flash
  157. $this->current_byte += 4;
  158. return $this->readMixedObject(); // return the body of mixed array
  159. }
  160. /**
  161. * readCustomClass reads the amf content associated with a class instance which was registered
  162. * with Object.registerClass. In order to preserve the class name an additional property is assigned
  163. * to the object "_explicitType". This property will be overwritten if it existed within the class already.
  164. *
  165. * @return object The php representation of the object
  166. */
  167. function readCustomClass() {
  168. $typeIdentifier = str_replace('..', '', $this->readUTF());
  169. $obj = $this->mapClass($typeIdentifier);
  170. $isObject = true;
  171. if($obj == NULL)
  172. {
  173. $obj = array();
  174. $isObject = false;
  175. }
  176. $this->amf0storedObjects[] = & $obj;
  177. $key = $this->readUTF(); // grab the key
  178. for ($type = $this->readByte(); $type != 9; $type = $this->readByte()) {
  179. $val = $this->readData($type); // grab the value
  180. if($isObject)
  181. {
  182. $obj->$key = $val; // save the name/value pair in the array
  183. }
  184. else
  185. {
  186. $obj[$key] = $val; // save the name/value pair in the array
  187. }
  188. $key = $this->readUTF(); // get the next name
  189. }
  190. if(!$isObject)
  191. {
  192. $obj['_explicitType'] = $typeIdentifier;
  193. }
  194. return $obj; // return the array
  195. }
  196. /**
  197. * readDate reads a date from the amf message and returns the time in ms.
  198. * This method is still under development.
  199. *
  200. * @return long The date in ms.
  201. */
  202. function readDate() {
  203. $ms = $this->readDouble(); // date in milliseconds from 01/01/1970
  204. $int = $this->readInt(); // nasty way to get timezone
  205. if ($int > 720) {
  206. $int = - (65536 - $int);
  207. }
  208. $int *= -60;
  209. //$int *= 1000;
  210. //$min = $int % 60;
  211. //$timezone = "GMT " . - $hr . ":" . abs($min);
  212. // end nastiness
  213. //We store the last timezone found in date fields in the request
  214. //FOr most purposes, it's expected that the timezones
  215. //don't change from one date object to the other (they change per client though)
  216. DateWrapper::setTimezone($int);
  217. return $ms;
  218. }
  219. /**
  220. * readReference replaces the old readFlushedSO. It treats where there
  221. * are references to other objects. Currently it does not resolve the
  222. * object as this would involve a serious amount of overhead, unless
  223. * you have a genius idea
  224. *
  225. * @return String
  226. */
  227. function readReference() {
  228. $reference = $this->readInt();
  229. return $this->amf0storedObjects[$reference];
  230. }
  231. function readAny()
  232. {
  233. if($this->native)
  234. return amfphp_decode($this->raw_data, $this->decodeFlags, $this->current_byte, array(& $this, "decodeCallback"));
  235. else
  236. {
  237. $type = $this->readByte(); // grab the type of the element
  238. return $this->readData($type); // turn the element into real data
  239. }
  240. }
  241. /**
  242. * readData is the main switch for mapping a type code to an actual
  243. * implementation for deciphering it.
  244. *
  245. * @param mixed $type The $type integer
  246. * @return mixed The php version of the data in the message block
  247. */
  248. function readData($type) {
  249. switch ($type) {
  250. case 0: // number
  251. $data = $this->readDouble();
  252. break;
  253. case 1: // boolean
  254. $data = $this->readByte() == 1;
  255. break;
  256. case 2: // string
  257. $data = $this->readUTF();
  258. break;
  259. case 3: // object Object
  260. $data = $this->readObject();
  261. break;
  262. case 5: // null
  263. $data = null;
  264. break;
  265. case 6: // undefined
  266. $data = null;
  267. break;
  268. case 7: // Circular references are returned here
  269. $data = $this->readReference();
  270. break;
  271. case 8: // mixed array with numeric and string keys
  272. $data = $this->readMixedArray();
  273. break;
  274. case 10: // array
  275. $data = $this->readArray();
  276. break;
  277. case 11: // date
  278. $data = $this->readDate();
  279. break;
  280. case 12: // string, strlen(string) > 2^16
  281. $data = $this->readLongUTF();
  282. break;
  283. case 13: // mainly internal AS objects
  284. $data = null;
  285. break;
  286. case 15: // XML
  287. $data = $this->readLongUTF();
  288. break;
  289. case 16: // Custom Class
  290. $data = $this->readCustomClass();
  291. break;
  292. case 17: //AMF3-specific
  293. $GLOBALS['amfphp']['encoding'] = "amf3";
  294. $data = $this->readAmf3Data();
  295. break;
  296. default: // unknown case
  297. trigger_error("Found unhandled type with code: $type");
  298. exit();
  299. break;
  300. }
  301. return $data;
  302. }
  303. /********************************************************************************
  304. * This is the AMF3 specific stuff
  305. ********************************************************************************/
  306. function readAmf3Data()
  307. {
  308. $type = $this->readByte();
  309. switch($type)
  310. {
  311. case 0x00 : return null; //undefined
  312. case 0x01 : return null; //null
  313. case 0x02 : return false; //boolean false
  314. case 0x03 : return true; //boolean true
  315. case 0x04 : return $this->readAmf3Int();
  316. case 0x05 : return $this->readDouble();
  317. case 0x06 : return $this->readAmf3String();
  318. case 0x07 : return $this->readAmf3XmlString();
  319. case 0x08 : return $this->readAmf3Date();
  320. case 0x09 : return $this->readAmf3Array();
  321. case 0x0A : return $this->readAmf3Object();
  322. case 0x0B : return $this->readAmf3XmlString();
  323. case 0x0C : return $this->readAmf3ByteArray();
  324. default: trigger_error("undefined Amf3 type encountered: " . $type, E_USER_ERROR);
  325. }
  326. }
  327. /// <summary>
  328. /// Handle decoding of the variable-length representation
  329. /// which gives seven bits of value per serialized byte by using the high-order bit
  330. /// of each byte as a continuation flag.
  331. /// </summary>
  332. /// <returns></returns>
  333. function readAmf3Int()
  334. {
  335. $int = $this->readByte();
  336. if($int < 128)
  337. return $int;
  338. else
  339. {
  340. $int = ($int & 0x7f) << 7;
  341. $tmp = $this->readByte();
  342. if($tmp < 128)
  343. {
  344. return $int | $tmp;
  345. }
  346. else
  347. {
  348. $int = ($int | ($tmp & 0x7f)) << 7;
  349. $tmp = $this->readByte();
  350. if($tmp < 128)
  351. {
  352. return $int | $tmp;
  353. }
  354. else
  355. {
  356. $int = ($int | ($tmp & 0x7f)) << 8;
  357. $tmp = $this->readByte();
  358. $int |= $tmp;
  359. // Check if the integer should be negative
  360. if (($int & 0x10000000) != 0) {
  361. // and extend the sign bit
  362. $int |= 0xe0000000;
  363. }
  364. return $int;
  365. }
  366. }
  367. }
  368. }
  369. function readAmf3Date()
  370. {
  371. $dateref = $this->readAmf3Int();
  372. if (($dateref & 0x01) == 0) {
  373. $dateref = $dateref >> 1;
  374. if ($dateref>=count($this->storedObjects)) {
  375. trigger_error('Undefined date reference: ' . $dateref, E_USER_ERROR);
  376. return false;
  377. }
  378. return $this->storedObjects[$dateref];
  379. }
  380. //$timeOffset = ($dateref >> 1) * 6000 * -1;
  381. $ms = $this->readDouble();
  382. //$date = $ms-$timeOffset;
  383. $date = $ms;
  384. $this->storedObjects[] = & $date;
  385. return $date;
  386. }
  387. /**
  388. * readString
  389. *
  390. * @return string
  391. */
  392. function readAmf3String() {
  393. $strref = $this->readAmf3Int();
  394. if (($strref & 0x01) == 0) {
  395. $strref = $strref >> 1;
  396. if ($strref >= count($this->storedStrings)) {
  397. trigger_error('Undefined string reference: ' . $strref, E_USER_ERROR);
  398. return false;
  399. }
  400. return $this->storedStrings[$strref];
  401. } else {
  402. $strlen = $strref >> 1;
  403. $str = "";
  404. if ($strlen > 0)
  405. {
  406. $str = $this->readBuffer($strlen);
  407. $this->storedStrings[] = $str;
  408. }
  409. return $str;
  410. }
  411. }
  412. function readAmf3XmlString()
  413. {
  414. $handle = $this->readAmf3Int();
  415. $inline = (($handle & 1) != 0 ); $handle = $handle >> 1;
  416. if( $inline )
  417. {
  418. $xml = $this->readBuffer($handle);
  419. $this->storedStrings[] = $xml;
  420. }
  421. else
  422. {
  423. $xml = $this->storedObjects[$handle];
  424. }
  425. return $xml;
  426. }
  427. function readAmf3ByteArray()
  428. {
  429. $handle = $this->readAmf3Int();
  430. $inline = (($handle & 1) != 0 );$handle = $handle >> 1;
  431. if( $inline )
  432. {
  433. $ba = new ByteArray($this->readBuffer($handle));
  434. $this->storedObjects[] = $ba;
  435. }
  436. else
  437. {
  438. $ba = $this->storedObjects[$handle];
  439. }
  440. return $ba;
  441. }
  442. function readAmf3Array()
  443. {
  444. $handle = $this->readAmf3Int();
  445. $inline = (($handle & 1) != 0 ); $handle = $handle >> 1;
  446. if( $inline )
  447. {
  448. $hashtable = array();
  449. $this->storedObjects[] = & $hashtable;
  450. $key = $this->readAmf3String();
  451. while( $key != "" )
  452. {
  453. $value = $this->readAmf3Data();
  454. $hashtable[$key] = $value;
  455. $key = $this->readAmf3String();
  456. }
  457. for($i = 0; $i < $handle; $i++)
  458. {
  459. //Grab the type for each element.
  460. $value = $this->readAmf3Data();
  461. $hashtable[$i] = $value;
  462. }
  463. return $hashtable;
  464. }
  465. else
  466. {
  467. return $this->storedObjects[$handle];
  468. }
  469. }
  470. function readAmf3Object()
  471. {
  472. $handle = $this->readAmf3Int();
  473. $inline = (($handle & 1) != 0 ); $handle = $handle >> 1;
  474. if( $inline )
  475. {
  476. //an inline object
  477. $inlineClassDef = (($handle & 1) != 0 );$handle = $handle >> 1;
  478. if( $inlineClassDef )
  479. {
  480. //inline class-def
  481. $typeIdentifier = $this->readAmf3String();
  482. $typedObject = !is_null($typeIdentifier) && $typeIdentifier != "";
  483. //flags that identify the way the object is serialized/deserialized
  484. $externalizable = (($handle & 1) != 0 );$handle = $handle >> 1;
  485. $dynamic = (($handle & 1) != 0 );$handle = $handle >> 1;
  486. $classMemberCount = $handle;
  487. $classMemberDefinitions = array();
  488. for($i = 0; $i < $classMemberCount; $i++)
  489. {
  490. $classMemberDefinitions[] = $this->readAmf3String();
  491. }
  492. //string mappedTypeName = typeIdentifier;
  493. //if( applicationContext != null )
  494. // mappedTypeName = applicationContext.GetMappedTypeName(typeIdentifier);
  495. $classDefinition = array("type" => $typeIdentifier, "members" => $classMemberDefinitions,
  496. "externalizable" => $externalizable, "dynamic" => $dynamic);
  497. $this->storedDefinitions[] = $classDefinition;
  498. }
  499. else
  500. {
  501. //a reference to a previously passed class-def
  502. $classDefinition = $this->storedDefinitions[$handle];
  503. }
  504. }
  505. else
  506. {
  507. //an object reference
  508. return $this->storedObjects[$handle];
  509. }
  510. $type = $classDefinition['type'];
  511. $obj = $this->mapClass($type);
  512. $isObject = true;
  513. if($obj == NULL)
  514. {
  515. $obj = array();
  516. $isObject = false;
  517. }
  518. //Add to references as circular references may search for this object
  519. $this->storedObjects[] = & $obj;
  520. if( $classDefinition['externalizable'] )
  521. {
  522. if($type == 'flex.messaging.io.ArrayCollection')
  523. {
  524. $obj = $this->readAmf3Data();
  525. }
  526. else if($type == 'flex.messaging.io.ObjectProxy')
  527. {
  528. $obj = $this->readAmf3Data();
  529. }
  530. else
  531. {
  532. trigger_error("Unable to read externalizable data type " . $type, E_USER_ERROR);
  533. }
  534. }
  535. else
  536. {
  537. $members = $classDefinition['members'];
  538. $memberCount = count($members);
  539. for($i = 0; $i < $memberCount; $i++)
  540. {
  541. $val = $this->readAmf3Data();
  542. $key = $members[$i];
  543. if($isObject)
  544. {
  545. $obj->$key = $val;
  546. }
  547. else
  548. {
  549. $obj[$key] = $val;
  550. }
  551. }
  552. if($classDefinition['dynamic']/* && obj is ASObject*/)
  553. {
  554. $key = $this->readAmf3String();
  555. while( $key != "" )
  556. {
  557. $value = $this->readAmf3Data();
  558. if($isObject)
  559. {
  560. $obj->$key = $value;
  561. }
  562. else
  563. {
  564. $obj[$key] = $value;
  565. }
  566. $key = $this->readAmf3String();
  567. }
  568. }
  569. if($type != '' && !$isObject)
  570. {
  571. $obj['_explicitType'] = $type;
  572. }
  573. }
  574. if($isObject && method_exists($obj, 'init'))
  575. {
  576. $obj->init();
  577. }
  578. return $obj;
  579. }
  580. /**
  581. * Taken from SabreAMF
  582. */
  583. function readBuffer($len)
  584. {
  585. $data = substr($this->raw_data,$this->current_byte,$len);
  586. $this->current_byte += $len;
  587. return $data;
  588. }
  589. }
  590. ?>