Deserializer.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Amf
  17. * @subpackage Parse_Amf3
  18. * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. */
  21. /** Zend_Amf_Parse_Deserializer */
  22. require_once 'Zend/Amf/Parse/Deserializer.php';
  23. /** Zend_Amf_Parse_TypeLoader */
  24. require_once 'Zend/Amf/Parse/TypeLoader.php';
  25. /**
  26. * Read an AMF3 input stream and convert it into PHP data types.
  27. *
  28. * @todo readObject to handle Typed Objects
  29. * @todo readXMLStrimg to be implemented.
  30. * @todo Class could be implmented as Factory Class with each data type it's own class.
  31. * @package Zend_Amf
  32. * @subpackage Parse_Amf3
  33. * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
  34. * @license http://framework.zend.com/license/new-bsd New BSD License
  35. */
  36. class Zend_Amf_Parse_Amf3_Deserializer extends Zend_Amf_Parse_Deserializer
  37. {
  38. /**
  39. * Total number of objects in the referenceObject array
  40. * @var int
  41. */
  42. protected $_objectCount;
  43. /**
  44. * An array of reference objects per amf body
  45. * @var array
  46. */
  47. protected $_referenceObjects = array();
  48. /**
  49. * An array of reference strings per amf body
  50. * @var array
  51. */
  52. protected $_referenceStrings = array();
  53. /**
  54. * An array of reference class definitions per body
  55. * @var array
  56. */
  57. protected $_referenceDefinitions = array();
  58. /**
  59. * Read AMF markers and dispatch for deserialization
  60. *
  61. * Checks for AMF marker types and calls the appropriate methods
  62. * for deserializing those marker types. markers are the data type of
  63. * the following value.
  64. *
  65. * @param integer $typeMarker
  66. * @return mixed Whatever the corresponding PHP data type is
  67. * @throws Zend_Amf_Exception for unidentified marker type
  68. */
  69. public function readTypeMarker($typeMarker = null)
  70. {
  71. if(null === $typeMarker) {
  72. $typeMarker = $this->_stream->readByte();
  73. }
  74. switch($typeMarker) {
  75. case Zend_Amf_Constants::AMF3_UNDEFINED:
  76. return null;
  77. case Zend_Amf_Constants::AMF3_NULL:
  78. return null;
  79. case Zend_Amf_Constants::AMF3_BOOLEAN_FALSE:
  80. return false;
  81. case Zend_Amf_Constants::AMF3_BOOLEAN_TRUE:
  82. return true;
  83. case Zend_Amf_Constants::AMF3_INTEGER:
  84. return $this->readInteger();
  85. case Zend_Amf_Constants::AMF3_NUMBER:
  86. return $this->_stream->readDouble();
  87. case Zend_Amf_Constants::AMF3_STRING:
  88. return $this->readString();
  89. case Zend_Amf_Constants::AMF3_DATE:
  90. return $this->readDate();
  91. case Zend_Amf_Constants::AMF3_ARRAY:
  92. return $this->readArray();
  93. case Zend_Amf_Constants::AMF3_OBJECT:
  94. return $this->readObject();
  95. case Zend_Amf_Constants::AMF3_XML:
  96. case Zend_Amf_Constants::AMF3_XMLSTRING:
  97. return $this->readXmlString();
  98. case Zend_Amf_Constants::AMF3_BYTEARRAY:
  99. return $this->readString();
  100. default:
  101. require_once 'Zend/Amf/Exception.php';
  102. throw new Zend_Amf_Exception('Unsupported type marker: ' . $typeMarker);
  103. }
  104. }
  105. /**
  106. * Read and deserialize an integer
  107. *
  108. * AMF 3 represents smaller integers with fewer bytes using the most
  109. * significant bit of each byte. The worst case uses 32-bits
  110. * to represent a 29-bit number, which is what we would have
  111. * done with no compression.
  112. * - 0x00000000 - 0x0000007F : 0xxxxxxx
  113. * - 0x00000080 - 0x00003FFF : 1xxxxxxx 0xxxxxxx
  114. * - 0x00004000 - 0x001FFFFF : 1xxxxxxx 1xxxxxxx 0xxxxxxx
  115. * - 0x00200000 - 0x3FFFFFFF : 1xxxxxxx 1xxxxxxx 1xxxxxxx xxxxxxxx
  116. * - 0x40000000 - 0xFFFFFFFF : throw range exception
  117. *
  118. *
  119. * 0x04 -> integer type code, followed by up to 4 bytes of data.
  120. *
  121. * @see: Parsing integers on OSFlash {http://osflash.org/amf3/parsing_integers>} for the AMF3 integer data format.
  122. * @return int|float
  123. */
  124. public function readInteger()
  125. {
  126. $count = 1;
  127. $intReference = $this->_stream->readByte();
  128. $result = 0;
  129. while ((($intReference & 0x80) != 0) && $count < 4) {
  130. $result <<= 7;
  131. $result |= ($intReference & 0x7f);
  132. $intReference = $this->_stream->readByte();
  133. $count++;
  134. }
  135. if ($count < 4) {
  136. $result <<= 7;
  137. $result |= $intReference;
  138. } else {
  139. // Use all 8 bits from the 4th byte
  140. $result <<= 8;
  141. $result |= $intReference;
  142. // Check if the integer should be negative
  143. if (($result & 0x10000000) != 0) {
  144. //and extend the sign bit
  145. $result |= 0xe0000000;
  146. }
  147. }
  148. return $result;
  149. }
  150. /**
  151. * Read and deserialize a string
  152. *
  153. * Strings can be sent as a reference to a previously
  154. * occurring String by using an index to the implicit string reference table.
  155. * Strings are encoding using UTF-8 - however the header may either
  156. * describe a string literal or a string reference.
  157. *
  158. * - string = 0x06 string-data
  159. * - string-data = integer-data [ modified-utf-8 ]
  160. * - modified-utf-8 = *OCTET
  161. *
  162. * @return String
  163. */
  164. public function readString()
  165. {
  166. $stringReference = $this->readInteger();
  167. //Check if this is a reference string
  168. if (($stringReference & 0x01) == 0) {
  169. // reference string
  170. $stringReference = $stringReference >> 1;
  171. if ($stringReference >= count($this->_referenceStrings)) {
  172. require_once 'Zend/Amf/Exception.php';
  173. throw new Zend_Amf_Exception('Undefined string reference: ' . $stringReference);
  174. }
  175. // reference string found
  176. return $this->_referenceStrings[$stringReference];
  177. }
  178. $length = $stringReference >> 1;
  179. if ($length) {
  180. $string = $this->_stream->readBytes($length);
  181. $this->_referenceStrings[] = $string;
  182. } else {
  183. $string = "";
  184. }
  185. return $string;
  186. }
  187. /**
  188. * Read and deserialize a date
  189. *
  190. * Data is the number of milliseconds elapsed since the epoch
  191. * of midnight, 1st Jan 1970 in the UTC time zone.
  192. * Local time zone information is not sent to flash.
  193. *
  194. * - date = 0x08 integer-data [ number-data ]
  195. *
  196. * @return Zend_Date
  197. */
  198. public function readDate()
  199. {
  200. $dateReference = $this->readInteger();
  201. if (($dateReference & 0x01) == 0) {
  202. $dateReference = $dateReference >> 1;
  203. if ($dateReference>=count($this->_referenceObjects)) {
  204. require_once 'Zend/Amf/Exception.php';
  205. throw new Zend_Amf_Exception('Undefined date reference: ' . $dateReference);
  206. }
  207. return $this->_referenceObjects[$dateReference];
  208. }
  209. $timestamp = floor($this->_stream->readDouble() / 1000);
  210. require_once 'Zend/Date.php';
  211. $dateTime = new Zend_Date((int) $timestamp);
  212. $this->_referenceObjects[] = $dateTime;
  213. return $dateTime;
  214. }
  215. /**
  216. * Read amf array to PHP array
  217. *
  218. * - array = 0x09 integer-data ( [ 1OCTET *amf3-data ] | [OCTET *amf3-data 1] | [ OCTET *amf-data ] )
  219. *
  220. * @return array
  221. */
  222. public function readArray()
  223. {
  224. $arrayReference = $this->readInteger();
  225. if (($arrayReference & 0x01)==0){
  226. $arrayReference = $arrayReference >> 1;
  227. if ($arrayReference>=count($this->_referenceObjects)) {
  228. require_once 'Zend/Amf/Exception.php';
  229. throw new Zend_Amf_Exception('Unknow array reference: ' . $arrayReference);
  230. }
  231. return $this->_referenceObjects[$arrayReference];
  232. }
  233. // Create a holder for the array in the reference list
  234. $data = array();
  235. $this->_referenceObjects[] =& $data;
  236. $key = $this->readString();
  237. // Iterating for string based keys.
  238. while ($key != '') {
  239. $data[$key] = $this->readTypeMarker();
  240. $key = $this->readString();
  241. }
  242. $arrayReference = $arrayReference >>1;
  243. //We have a dense array
  244. for ($i=0; $i < $arrayReference; $i++) {
  245. $data[] = $this->readTypeMarker();
  246. }
  247. return $data;
  248. }
  249. /**
  250. * Read an object from the AMF stream and convert it into a PHP object
  251. *
  252. * @todo Rather than using an array of traitsInfo create Zend_Amf_Value_TraitsInfo
  253. * @return object|array
  254. */
  255. public function readObject()
  256. {
  257. $traitsInfo = $this->readInteger();
  258. $storedObject = ($traitsInfo & 0x01)==0;
  259. $traitsInfo = $traitsInfo >> 1;
  260. // Check if the Object is in the stored Objects reference table
  261. if ($storedObject) {
  262. $ref = $traitsInfo;
  263. if (!isset($this->_referenceObjects[$ref])) {
  264. require_once 'Zend/Amf/Exception.php';
  265. throw new Zend_Amf_Exception('Unknown Object reference: ' . $ref);
  266. }
  267. $returnObject = $this->_referenceObjects[$ref];
  268. } else {
  269. // Check if the Object is in the stored Definistions reference table
  270. $storedClass = ($traitsInfo & 0x01) == 0;
  271. $traitsInfo = $traitsInfo >> 1;
  272. if ($storedClass) {
  273. $ref = $traitsInfo;
  274. if (!isset($this->_referenceDefinitions[$ref])) {
  275. require_once 'Zend/Amf/Exception.php';
  276. throw new Zend_Amf_Exception('Unknows Definition reference: '. $ref);
  277. }
  278. // Populate the reference attributes
  279. $className = $this->_referenceDefinitions[$ref]['className'];
  280. $encoding = $this->_referenceDefinitions[$ref]['encoding'];
  281. $propertyNames = $this->_referenceDefinitions[$ref]['propertyNames'];
  282. } else {
  283. // The class was not in the reference tables. Start reading rawdata to build traits.
  284. // Create a traits table. Zend_Amf_Value_TraitsInfo would be ideal
  285. $className = $this->readString();
  286. $encoding = $traitsInfo & 0x03;
  287. $propertyNames = array();
  288. $traitsInfo = $traitsInfo >> 2;
  289. }
  290. // We now have the object traits defined in variables. Time to go to work:
  291. if (!$className) {
  292. // No class name generic object
  293. $returnObject = new stdClass();
  294. } else {
  295. // Defined object
  296. // Typed object lookup agsinst registered classname maps
  297. if ($loader = Zend_Amf_Parse_TypeLoader::loadType($className)) {
  298. $returnObject = new $loader();
  299. } else {
  300. //user defined typed object
  301. require_once 'Zend/Amf/Exception.php';
  302. throw new Zend_Amf_Exception('Typed object not found: '. $className . ' ');
  303. }
  304. }
  305. // Add the Object ot the reference table
  306. $this->_referenceObjects[] = $returnObject;
  307. $properties = array(); // clear value
  308. // Check encoding types for additional processing.
  309. switch ($encoding) {
  310. case (Zend_Amf_Constants::ET_EXTERNAL):
  311. // Externalizable object such as {ArrayCollection} and {ObjectProxy}
  312. if (!$storedClass) {
  313. $this->_referenceDefinitions[] = array(
  314. 'className' => $className,
  315. 'encoding' => $encoding,
  316. 'propertyNames' => $propertyNames,
  317. );
  318. }
  319. $returnObject->externalizedData = $this->readTypeMarker();
  320. break;
  321. case (Zend_Amf_Constants::ET_DYNAMIC):
  322. // used for Name-value encoding
  323. if (!$storedClass) {
  324. $this->_referenceDefinitions[] = array(
  325. 'className' => $className,
  326. 'encoding' => $encoding,
  327. 'propertyNames' => $propertyNames,
  328. );
  329. }
  330. // not a refrence object read name value properties from byte stream
  331. do {
  332. $property = $this->readString();
  333. if ($property != "") {
  334. $propertyNames[] = $property;
  335. $properties[$property] = $this->readTypeMarker();
  336. }
  337. } while ($property !="");
  338. break;
  339. default:
  340. // basic property list object.
  341. if (!$storedClass) {
  342. $count = $traitsInfo; // Number of properties in the list
  343. for($i=0; $i< $count; $i++) {
  344. $propertyNames[] = $this->readString();
  345. }
  346. // Add a refrence to the class.
  347. $this->_referenceDefinitions[] = array(
  348. 'className' => $className,
  349. 'encoding' => $encoding,
  350. 'propertyNames' => $propertyNames,
  351. );
  352. }
  353. foreach ($propertyNames as $property) {
  354. $properties[$property] = $this->readTypeMarker();
  355. }
  356. break;
  357. }
  358. // Add properties back to the return object.
  359. foreach($properties as $key=>$value) {
  360. if($key) {
  361. $returnObject->$key = $value;
  362. }
  363. }
  364. }
  365. if($returnObject instanceof Zend_Amf_Value_Messaging_ArrayCollection) {
  366. if(isset($returnObject->externalizedData)) {
  367. $returnObject = $returnObject->externalizedData;
  368. } else {
  369. $returnObject = get_object_vars($returnObject);
  370. }
  371. }
  372. return $returnObject;
  373. }
  374. /**
  375. * Convert XML to SimpleXml
  376. * If user wants DomDocument they can use dom_import_simplexml
  377. *
  378. * @return SimpleXml Object
  379. */
  380. public function readXmlString()
  381. {
  382. $xmlReference = $this->readInteger();
  383. $length = $xmlReference >> 1;
  384. $string = $this->_stream->readBytes($length);
  385. return simplexml_load_string($string);
  386. }
  387. }