Deserializer.php 15 KB

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