Deserializer.php 15 KB

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