PageRenderTime 52ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/inc/amfphp/Amfphp/Core/Amf/Serializer.php

https://github.com/EmranAhmed/wp-easycart
PHP | 905 lines | 659 code | 50 blank | 196 comment | 52 complexity | b42722da61d565879d465f91049bf2a2 MD5 | raw file
  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. * @package Amfphp_Core_Amf
  10. */
  11. /**
  12. * AmfSerializer manages the job of translating PHP objects into
  13. * the actionscript equivalent via Amf. The main method of the serializer
  14. * is the serialize method which takes and AmfObject as it's argument
  15. * and builds the resulting Amf Message.
  16. *
  17. * @package Amfphp_Core_Amf
  18. */
  19. class Amfphp_Core_Amf_Serializer implements Amfphp_Core_Common_ISerializer {
  20. /**
  21. *
  22. * @var String the output stream
  23. */
  24. protected $outBuffer;
  25. /**
  26. * packet
  27. * @var Amfphp_Core_Amf_Packet
  28. */
  29. protected $packet;
  30. /**
  31. * the maximum amount of objects stored for reference
  32. */
  33. const MAX_STORED_OBJECTS = 1024;
  34. /**
  35. *
  36. * used for Amf0 references
  37. * @var array
  38. */
  39. protected $Amf0StoredObjects;
  40. /**
  41. *
  42. * used for Amf3 references
  43. * @var array
  44. */
  45. protected $storedObjects;
  46. /**
  47. * amf3 references to strings
  48. * @var array
  49. */
  50. protected $storedStrings;
  51. /**
  52. * used for traits references. key: class name. value: array(reference id, array(property names))
  53. * @var array
  54. */
  55. protected $className2TraitsInfo;
  56. /**
  57. * converts from php object to binary
  58. * @param Amfphp_Core_Amf_Packet $data
  59. */
  60. public function serialize($data) {
  61. $this->packet = $data;
  62. $this->resetReferences();
  63. $this->writeInt(0); // write the version (always 0)
  64. $count = count($this->packet->headers);
  65. $this->writeInt($count); // write header count
  66. for ($i = 0; $i < $count; $i++) {
  67. $this->resetReferences();
  68. //write header
  69. $header = $this->packet->headers[$i];
  70. $this->writeUTF($header->name);
  71. if ($header->required) {
  72. $this->writeByte(1);
  73. } else {
  74. $this->writeByte(0);
  75. }
  76. $tempBuf = $this->outBuffer;
  77. $this->outBuffer = '';
  78. $this->writeData($header->data);
  79. $serializedHeader = $this->outBuffer;
  80. $this->outBuffer = $tempBuf;
  81. $this->writeLong(strlen($serializedHeader));
  82. $this->outBuffer .= $serializedHeader;
  83. }
  84. $count = count($this->packet->messages);
  85. $this->writeInt($count); // write the Message count
  86. for ($i = 0; $i < $count; $i++) {
  87. $this->resetReferences();
  88. //write body.
  89. $message = $this->packet->messages[$i];
  90. $this->writeUTF($message->targetUri);
  91. $this->writeUTF($message->responseUri);
  92. //save the current buffer, and flush it to write the Message
  93. $tempBuf = $this->outBuffer;
  94. $this->outBuffer = '';
  95. $this->writeData($message->data);
  96. $serializedMessage = $this->outBuffer;
  97. $this->outBuffer = $tempBuf;
  98. $this->writeLong(strlen($serializedMessage));
  99. $this->outBuffer .= $serializedMessage;
  100. }
  101. return $this->outBuffer;
  102. }
  103. /**
  104. * initialize reference arrays and counters. Call before writing a body or a header, as the indices are local to each message body or header
  105. */
  106. protected function resetReferences() {
  107. $this->Amf0StoredObjects = array();
  108. $this->storedStrings = array();
  109. $this->storedObjects = array();
  110. $this->className2TraitsInfo = array();
  111. }
  112. /**
  113. * get serialized data output
  114. * @return string
  115. */
  116. public function getOutput() {
  117. return $this->outBuffer;
  118. }
  119. /**
  120. * writeByte writes a singe byte to the output stream
  121. * 0-255 range
  122. *
  123. * @param int $b An int that can be converted to a byte
  124. */
  125. protected function writeByte($b) {
  126. $this->outBuffer .= pack('c', $b); // use pack with the c flag
  127. }
  128. /**
  129. * writeInt takes an int and writes it as 2 bytes to the output stream
  130. * 0-65535 range
  131. *
  132. * @param int $n An integer to convert to a 2 byte binary string
  133. */
  134. protected function writeInt($n) {
  135. $this->outBuffer .= pack('n', $n); // use pack with the n flag
  136. }
  137. /**
  138. * writeLong takes an int, float or double and converts it to a 4 byte binary string and
  139. * adds it to the output buffer
  140. *
  141. * @param long $l A long to convert to a 4 byte binary string
  142. */
  143. protected function writeLong($l) {
  144. $this->outBuffer .= pack('N', $l); // use pack with the N flag
  145. }
  146. /**
  147. * writeDouble takes a float as the input and writes it to the output stream.
  148. * Then if the system is big-endian, it reverses the bytes order because all
  149. * doubles passed via remoting are passed little-endian.
  150. *
  151. * @param double $d The double to add to the output buffer
  152. */
  153. protected function writeDouble($d) {
  154. $b = pack('d', $d); // pack the bytes
  155. if (Amfphp_Core_Amf_Util::isSystemBigEndian()) { // if we are a big-endian processor
  156. $r = strrev($b);
  157. } else { // add the bytes to the output
  158. $r = $b;
  159. }
  160. $this->outBuffer .= $r;
  161. }
  162. /**
  163. * writeUTF takes and input string, writes the length as an int and then
  164. * appends the string to the output buffer
  165. *
  166. * @param string $s The string less than 65535 characters to add to the stream
  167. */
  168. protected function writeUtf($s) {
  169. $this->writeInt(strlen($s)); // write the string length - max 65535
  170. $this->outBuffer .= $s; // write the string chars
  171. }
  172. /**
  173. * writeLongUTF will write a string longer than 65535 characters.
  174. * It works exactly as writeUTF does except uses a long for the length
  175. * flag.
  176. *
  177. * @param string $s A string to add to the byte stream
  178. */
  179. protected function writeLongUtf($s) {
  180. $this->writeLong(strlen($s));
  181. $this->outBuffer .= $s; // write the string chars
  182. }
  183. /**
  184. * writeBoolean writes the boolean code (0x01) and the data to the output stream
  185. *
  186. * @param bool $d The boolean value
  187. */
  188. protected function writeBoolean($d) {
  189. $this->writeByte(1); // write the 'boolean-marker'
  190. $this->writeByte($d); // write the boolean byte (0 = FALSE; rest = TRUE)
  191. }
  192. /**
  193. * writeString writes the string code (0x02) and the UTF8 encoded
  194. * string to the output stream.
  195. * Note: strings are truncated to 64k max length. Use XML as type
  196. * to send longer strings
  197. *
  198. * @param string $d The string data
  199. */
  200. protected function writeString($d) {
  201. $count = strlen($d);
  202. if ($count < 65536) {
  203. $this->writeByte(2);
  204. $this->writeUTF($d);
  205. } else {
  206. $this->writeByte(12);
  207. $this->writeLongUTF($d);
  208. }
  209. }
  210. /**
  211. * writeXML writes the xml code (0x0F) and the XML string to the output stream
  212. * Note: strips whitespace
  213. * @param Amfphp_Core_Amf_Types_Xml $d
  214. */
  215. protected function writeXML(Amfphp_Core_Amf_Types_Xml $d) {
  216. if (!$this->handleReference($d->data, $this->Amf0StoredObjects)) {
  217. $this->writeByte(0x0F);
  218. $this->writeLongUTF(preg_replace('/\>(\n|\r|\r\n| |\t)*\</', '><', trim($d->data)));
  219. }
  220. }
  221. /**
  222. * writeDate writes the date code (0x0B) and the date value (milliseconds from 1 January 1970) to the output stream, along with an empty unsupported timezone
  223. *
  224. * @param Amfphp_Core_Amf_Types_Date $d The date value
  225. */
  226. protected function writeDate(Amfphp_Core_Amf_Types_Date $d) {
  227. $this->writeByte(0x0B);
  228. $this->writeDouble($d->timeStamp);
  229. $this->writeInt(0);
  230. }
  231. /**
  232. * writeNumber writes the number code (0x00) and the numeric data to the output stream
  233. * All numbers passed through remoting are floats.
  234. *
  235. * @param int $d The numeric data
  236. */
  237. protected function writeNumber($d) {
  238. $this->writeByte(0); // write the number code
  239. $this->writeDouble(floatval($d)); // write the number as a double
  240. }
  241. /**
  242. * writeNull writes the null code (0x05) to the output stream
  243. */
  244. protected function writeNull() {
  245. $this->writeByte(5); // null is only a 0x05 flag
  246. }
  247. /**
  248. * writeUndefined writes the Undefined code (0x06) to the output stream
  249. */
  250. protected function writeUndefined() {
  251. $this->writeByte(6); // Undefined is only a 0x06 flag
  252. }
  253. /**
  254. * writeObjectEnd writes the object end code (0x009) to the output stream
  255. */
  256. protected function writeObjectEnd() {
  257. $this->writeInt(0); // write the end object flag 0x00, 0x00, 0x09
  258. $this->writeByte(9);
  259. }
  260. /**
  261. * writeArrayOrObject first determines if the PHP array contains all numeric indexes
  262. * or a mix of keys. Then it either writes the array code (0x0A) or the
  263. * object code (0x03) and then the associated data.
  264. *
  265. * @param array $d The php array
  266. */
  267. protected function writeArrayOrObject($d) {
  268. // referencing is disabled in arrays
  269. //Because if the array contains only primitive values,
  270. //Then === will say that the two arrays are strictly equal
  271. //if they contain the same values, even if they are really distinct
  272. $count = count($this->Amf0StoredObjects);
  273. if ($count <= self::MAX_STORED_OBJECTS) {
  274. $this->Amf0StoredObjects[$count] = & $d;
  275. }
  276. $numeric = array(); // holder to store the numeric keys
  277. $string = array(); // holder to store the string keys
  278. $len = count($d); // get the total number of entries for the array
  279. $largestKey = -1;
  280. foreach ($d as $key => $data) { // loop over each element
  281. if (is_int($key) && ($key >= 0)) { // make sure the keys are numeric
  282. $numeric[$key] = $data; // The key is an index in an array
  283. $largestKey = max($largestKey, $key);
  284. } else {
  285. $string[$key] = $data; // The key is a property of an object
  286. }
  287. }
  288. $num_count = count($numeric); // get the number of numeric keys
  289. $str_count = count($string); // get the number of string keys
  290. if (($num_count > 0 && $str_count > 0) ||
  291. ($num_count > 0 && $largestKey != $num_count - 1)) { // this is a mixed array
  292. $this->writeByte(8); // write the mixed array code
  293. $this->writeLong($num_count); // write the count of items in the array
  294. $this->writeObjectFromArray($numeric + $string); // write the numeric and string keys in the mixed array
  295. } else if ($num_count > 0) { // this is just an array
  296. $num_count = count($numeric); // get the new count
  297. $this->writeByte(10); // write the array code
  298. $this->writeLong($num_count); // write the count of items in the array
  299. for ($i = 0; $i < $num_count; $i++) { // write all of the array elements
  300. $this->writeData($numeric[$i]);
  301. }
  302. } else if ($str_count > 0) { // this is an object
  303. $this->writeByte(3); // this is an object so write the object code
  304. $this->writeObjectFromArray($string); // write the object name/value pairs
  305. } else { //Patch submitted by Jason Justman
  306. $this->writeByte(10); // make this an array still
  307. $this->writeInt(0); // give it 0 elements
  308. $this->writeInt(0); // give it an element pad, this looks like a bug in Flash,
  309. //but keeps the next alignment proper
  310. }
  311. }
  312. /**
  313. * write reference
  314. * @param int $num
  315. */
  316. protected function writeReference($num) {
  317. $this->writeByte(0x07);
  318. $this->writeInt($num);
  319. }
  320. /**
  321. * writeObjectFromArray handles writing a php array with string or mixed keys. It does
  322. * not write the object code as that is handled by the writeArrayOrObject and this method
  323. * is shared with the CustomClass writer which doesn't use the object code.
  324. *
  325. * @param array $d The php array with string keys
  326. */
  327. protected function writeObjectFromArray($d) {
  328. foreach ($d as $key => $data) { // loop over each element
  329. $this->writeUTF($key); // write the name of the object
  330. $this->writeData($data); // write the value of the object
  331. }
  332. $this->writeObjectEnd();
  333. }
  334. /**
  335. * handles writing an anoynous object (stdClass)
  336. * can also be a reference
  337. *
  338. * @param stdClass $d The php object to write
  339. */
  340. protected function writeAnonymousObject($d) {
  341. if (!$this->handleReference($d, $this->Amf0StoredObjects)) {
  342. $this->writeByte(3);
  343. foreach ($d as $key => $data) { // loop over each element
  344. if ($key[0] != "\0") {
  345. $this->writeUTF($key); // write the name of the object
  346. $this->writeData($data); // write the value of the object
  347. }
  348. }
  349. $this->writeObjectEnd();
  350. }
  351. }
  352. /**
  353. * writeTypedObject takes an instance of a class and writes the variables defined
  354. * in it to the output stream.
  355. * To accomplish this we just blanket grab all of the object vars with get_object_vars, minus the Amfphp_Core_Amf_Constants::FIELD_EXPLICIT_TYPE field, whiuch is used as class name
  356. *
  357. * @param object $d The object to serialize the properties. The deserializer looks for Amfphp_Core_Amf_Constants::FIELD_EXPLICIT_TYPE on this object and writes it as the class name.
  358. */
  359. protected function writeTypedObject($d) {
  360. if ($this->handleReference($d, $this->Amf0StoredObjects)) {
  361. return;
  362. }
  363. $this->writeByte(16); // write the custom class code
  364. $explicitTypeField = Amfphp_Core_Amf_Constants::FIELD_EXPLICIT_TYPE;
  365. $className = $d->$explicitTypeField;
  366. if (!$className) {
  367. throw new Amfphp_Core_Exception(Amfphp_Core_Amf_Constants::FIELD_EXPLICIT_TYPE . ' not found on a object that is to be sent as typed. ' . print_r($d, true));
  368. }
  369. unset($d->$explicitTypeField);
  370. $this->writeUTF($className); // write the class name
  371. $objVars = $d;
  372. foreach ($objVars as $key => $data) { // loop over each element
  373. if ($key[0] != "\0") {
  374. $this->writeUTF($key); // write the name of the object
  375. $this->writeData($data); // write the value of the object
  376. }
  377. }
  378. $this->writeObjectEnd();
  379. }
  380. /**
  381. * writeData checks to see if the type was declared and then either
  382. * auto negotiates the type or relies on the user defined type to
  383. * serialize the data into Amf
  384. *
  385. * @param mixed $d The data
  386. */
  387. protected function writeData($d) {
  388. if ($this->packet->amfVersion == Amfphp_Core_Amf_Constants::AMF3_ENCODING) { //amf3 data. This is most often, so it's has been moved to the top to be first
  389. $this->writeByte(0x11);
  390. $this->writeAmf3Data($d);
  391. return;
  392. } elseif (is_int($d) || is_float($d)) { // double
  393. $this->writeNumber($d);
  394. return;
  395. } elseif (is_string($d)) { // string, long string
  396. $this->writeString($d);
  397. return;
  398. } elseif (is_bool($d)) { // boolean
  399. $this->writeBoolean($d);
  400. return;
  401. } elseif (is_null($d)) { // null
  402. $this->writeNull();
  403. return;
  404. } elseif (Amfphp_Core_Amf_Util::is_undefined($d)) { // undefined
  405. $this->writeUndefined();
  406. return;
  407. } elseif (is_array($d)) { // array
  408. $this->writeArrayOrObject($d);
  409. return;
  410. } elseif (Amfphp_Core_Amf_Util::is_date($d)) { // date
  411. $this->writeDate($d);
  412. return;
  413. } elseif (Amfphp_Core_Amf_Util::is_Xml($d)) { // Xml (note, no XmlDoc in AMF0)
  414. $this->writeXML($d);
  415. return;
  416. } elseif (is_object($d)) {
  417. $explicitTypeField = Amfphp_Core_Amf_Constants::FIELD_EXPLICIT_TYPE;
  418. if (isset($d->$explicitTypeField)) {
  419. $this->writeTypedObject($d);
  420. return;
  421. } else {
  422. $this->writeAnonymousObject($d);
  423. return;
  424. }
  425. }
  426. throw new Amfphp_Core_Exception("couldn't write data " . print_r($d));
  427. }
  428. /* * ******************************************************************************
  429. * Amf3 related code
  430. * ***************************************************************************** */
  431. /**
  432. * write amf 3 data
  433. * @todo no type markers ("\6', for example) in this method!
  434. * @param mixed $d
  435. */
  436. protected function writeAmf3Data($d) {
  437. if (is_int($d)) { //int
  438. $this->writeAmf3Number($d);
  439. return;
  440. } elseif (is_float($d)) { //double
  441. $this->outBuffer .= "\5";
  442. $this->writeDouble($d);
  443. return;
  444. } elseif (is_string($d)) { // string
  445. $this->outBuffer .= "\6";
  446. $this->writeAmf3String($d);
  447. return;
  448. } elseif (is_bool($d)) { // boolean
  449. $this->writeAmf3Bool($d);
  450. return;
  451. } elseif (is_null($d)) { // null
  452. $this->writeAmf3Null();
  453. return;
  454. } elseif (Amfphp_Core_Amf_Util::is_undefined($d)) { // undefined
  455. $this->writeAmf3Undefined();
  456. return;
  457. } elseif (Amfphp_Core_Amf_Util::is_date($d)) { // date
  458. $this->writeAmf3Date($d);
  459. return;
  460. } elseif (is_array($d)) { // array
  461. $this->writeAmf3Array($d);
  462. return;
  463. } elseif (Amfphp_Core_Amf_Util::is_byteArray($d)) { //byte array
  464. $this->writeAmf3ByteArray($d);
  465. return;
  466. } elseif (Amfphp_Core_Amf_Util::is_Xml($d)) { // Xml
  467. $this->writeAmf3Xml($d);
  468. return;
  469. } elseif (Amfphp_Core_Amf_Util::is_XmlDocument($d)) { // XmlDoc
  470. $this->writeAmf3XmlDocument($d);
  471. return;
  472. } elseif (is_object($d)) {
  473. $explicitTypeField = Amfphp_Core_Amf_Constants::FIELD_EXPLICIT_TYPE;
  474. if (isset($d->$explicitTypeField)) {
  475. $this->writeAmf3TypedObject($d);
  476. return;
  477. } else {
  478. $this->writeAmf3AnonymousObject($d);
  479. return;
  480. }
  481. }
  482. throw new Amfphp_Core_Exception("couldn't write object " . print_r($d, false));
  483. }
  484. /**
  485. * Write undefined (Amf3).
  486. *
  487. * @return nothing
  488. */
  489. protected function writeAmf3Undefined() {
  490. $this->outBuffer .= "\0";
  491. }
  492. /**
  493. * Write NULL (Amf3).
  494. *
  495. * @return nothing
  496. */
  497. protected function writeAmf3Null() {
  498. $this->outBuffer .= "\1";
  499. }
  500. /**
  501. * Write a boolean (Amf3).
  502. *
  503. * @param bool $d the boolean to serialise
  504. *
  505. * @return nothing
  506. */
  507. protected function writeAmf3Bool($d) {
  508. $this->outBuffer .= $d ? "\3" : "\2";
  509. }
  510. /**
  511. * Write an (un-)signed integer (Amf3).
  512. *
  513. * @see getAmf3Int()
  514. *
  515. * @param int $d the integer to serialise
  516. *
  517. * @return nothing
  518. */
  519. protected function writeAmf3Int($d) {
  520. $this->outBuffer .= $this->getAmf3Int($d);
  521. }
  522. /**
  523. * Write a string (Amf3). Strings are stored in a cache and in case the same string
  524. * is written again, a reference to the string is sent instead of the string itself.
  525. *
  526. * note: Sending strings larger than 268435455 (2^28-1 byte) will (silently) fail!
  527. *
  528. * note: The string marker is NOT sent here and has to be sent before, if needed.
  529. *
  530. *
  531. * @param string $d the string to send
  532. *
  533. * @return The reference index inside the lookup table is returned. In case of an empty
  534. * string which is sent in a special way, NULL is returned.
  535. */
  536. protected function writeAmf3String($d) {
  537. if ($d === '') {
  538. //Write 0x01 to specify the empty string ('UTF-8-empty')
  539. $this->outBuffer .= "\1";
  540. return;
  541. }
  542. if (!$this->handleReference($d, $this->storedStrings)) {
  543. $this->writeAmf3Int(strlen($d) << 1 | 1); // U29S-value
  544. $this->outBuffer .= $d;
  545. }
  546. }
  547. /**
  548. * handles writing an anoynous object (stdClass)
  549. * can also be a reference
  550. * Also creates a bogus traits entry, as even an anonymous object has traits. In this way a reference to a class trait will have the right id.
  551. * @todo it would seem that to create only one traits entry for an anonymous object would be the way to go. this
  552. * however messes things up in both Flash and Charles Proxy. For testing call discovery service using AMF. investigate.
  553. *
  554. * @param stdClass $d The php object to write
  555. * @param doReference Boolean This is used by writeAmf3Array, where the reference has already been taken care of,
  556. * so there this method is called with false
  557. */
  558. protected function writeAmf3AnonymousObject($d, $doReference = true) {
  559. //Write the object tag
  560. $this->outBuffer .= "\12";
  561. if ($doReference && $this->handleReference($d, $this->storedObjects)) {
  562. return;
  563. }
  564. //bogus class traits entry
  565. $this->className2TraitsInfo[] = array();
  566. //anonymous object. So type this as a dynamic object with no sealed members.
  567. //U29O-traits : 1011.
  568. $this->writeAmf3Int(0xB);
  569. //no class name. empty string for anonymous object
  570. $this->writeAmf3String("");
  571. //name/value pairs for dynamic properties
  572. foreach ($d as $key => $value) {
  573. $this->writeAmf3String($key);
  574. $this->writeAmf3Data($value);
  575. }
  576. //empty string, marks end of dynamic members
  577. $this->outBuffer .= "\1";
  578. }
  579. /**
  580. * write amf3 array
  581. * @param array $d
  582. */
  583. protected function writeAmf3Array(array $d) {
  584. // referencing is disabled in arrays
  585. //Because if the array contains only primitive values,
  586. //Then === will say that the two arrays are strictly equal
  587. //if they contain the same values, even if they are really distinct
  588. $count = count($this->storedObjects);
  589. if ($count <= self::MAX_STORED_OBJECTS) {
  590. $this->storedObjects[$count] = & $d;
  591. }
  592. $numeric = array(); // holder to store the numeric keys >= 0
  593. $string = array(); // holder to store the string keys; actually, non-integer or integer < 0 are stored
  594. $len = count($d); // get the total number of entries for the array
  595. $largestKey = -1;
  596. foreach ($d as $key => $data) { // loop over each element
  597. if (is_int($key) && ($key >= 0)) { // make sure the keys are numeric
  598. $numeric[$key] = $data; // The key is an index in an array
  599. $largestKey = max($largestKey, $key);
  600. } else {
  601. $string[$key] = $data; // The key is a property of an object
  602. }
  603. }
  604. $num_count = count($numeric); // get the number of numeric keys
  605. $str_count = count($string); // get the number of string keys
  606. if (
  607. ($str_count > 0 && $num_count == 0) || // Only strings or negative integer keys are present.
  608. ($num_count > 0 && $largestKey != $num_count - 1) // Non-negative integer keys are present, but the array is not 'dense' (it has gaps).
  609. ) {
  610. //// this is a mixed array. write it as an anonymous/dynamic object with no sealed members
  611. $this->writeAmf3AnonymousObject($numeric + $string, false);
  612. } else { // this is just an array
  613. $this->outBuffer .= "\11";
  614. $num_count = count($numeric);
  615. $handle = $num_count * 2 + 1;
  616. $this->writeAmf3Int($handle);
  617. foreach ($string as $key => $val) {
  618. $this->writeAmf3String($key);
  619. $this->writeAmf3Data($val);
  620. }
  621. $this->writeAmf3String(''); //End start hash
  622. for ($i = 0; $i < $num_count; $i++) {
  623. $this->writeAmf3Data($numeric[$i]);
  624. }
  625. }
  626. }
  627. /**
  628. * Return the serialisation of the given integer (Amf3).
  629. *
  630. * note: There does not seem to be a way to distinguish between signed and unsigned integers.
  631. * This method just sends the lowest 29 bit as-is, and the receiver is responsible to interpret
  632. * the result as signed or unsigned based on some context.
  633. *
  634. * note: The limit imposed by Amf3 is 29 bit. So in case the given integer is longer than 29 bit,
  635. * only the lowest 29 bits will be serialised. No error will be logged!
  636. * @TODO refactor into writeAmf3Int
  637. *
  638. * @param int $d the integer to serialise
  639. *
  640. * @return string
  641. */
  642. protected function getAmf3Int($d) {
  643. /**
  644. * @todo The lowest 29 bits are kept and all upper bits are removed. In case of
  645. * an integer larger than 29 bits (32 bit, 64 bit, etc.) the value will effectively change! Maybe throw an exception!
  646. */
  647. $d &= 0x1fffffff;
  648. if ($d < 0x80) {
  649. return
  650. chr($d);
  651. } elseif ($d < 0x4000) {
  652. return
  653. chr($d >> 7 & 0x7f | 0x80) .
  654. chr($d & 0x7f);
  655. } elseif ($d < 0x200000) {
  656. return
  657. chr($d >> 14 & 0x7f | 0x80) .
  658. chr($d >> 7 & 0x7f | 0x80) .
  659. chr($d & 0x7f);
  660. } else {
  661. return
  662. chr($d >> 22 & 0x7f | 0x80) .
  663. chr($d >> 15 & 0x7f | 0x80) .
  664. chr($d >> 8 & 0x7f | 0x80) .
  665. chr($d & 0xff);
  666. }
  667. }
  668. /**
  669. * write Amf3 Number
  670. * @param number $d
  671. */
  672. protected function writeAmf3Number($d) {
  673. if (is_int($d) && $d >= -268435456 && $d <= 268435455) {//check valid range for 29bits
  674. $this->outBuffer .= "\4";
  675. $this->writeAmf3Int($d);
  676. } else {
  677. //overflow condition would occur upon int conversion
  678. $this->outBuffer .= "\5";
  679. $this->writeDouble($d);
  680. }
  681. }
  682. /**
  683. * write Amfphp_Core_Amf_Types_Xml in amf3
  684. * @param Amfphp_Core_Amf_Types_Xml $d
  685. */
  686. protected function writeAmf3Xml(Amfphp_Core_Amf_Types_Xml $d) {
  687. $d = preg_replace('/\>(\n|\r|\r\n| |\t)*\</', '><', trim($d->data));
  688. $this->writeByte(0x0B);
  689. $this->writeAmf3String($d);
  690. }
  691. /**
  692. * write Amfphp_Core_Amf_Types_XmlDocument in amf3
  693. * @param Amfphp_Core_Amf_Types_XmlDocument $d
  694. */
  695. protected function writeAmf3XmlDocument(Amfphp_Core_Amf_Types_XmlDocument $d) {
  696. $d = preg_replace('/\>(\n|\r|\r\n| |\t)*\</', '><', trim($d->data));
  697. $this->writeByte(0x07);
  698. $this->writeAmf3String($d);
  699. }
  700. /**
  701. * write Amfphp_Core_Amf_Types_Date in amf 3
  702. * @param Amfphp_Core_Amf_Types_Date $d
  703. */
  704. protected function writeAmf3Date(Amfphp_Core_Amf_Types_Date $d) {
  705. $this->writeByte(0x08);
  706. $this->writeAmf3Int(1);
  707. $this->writeDouble($d->timeStamp);
  708. }
  709. /**
  710. * write Amfphp_Core_Amf_Types_ByteArray in amf3
  711. * @param Amfphp_Core_Amf_Types_ByteArray $d
  712. */
  713. protected function writeAmf3ByteArray(Amfphp_Core_Amf_Types_ByteArray $d) {
  714. $this->writeByte(0x0C);
  715. $data = $d->data;
  716. if (!$this->handleReference($data, $this->storedObjects)) {
  717. $obj_length = strlen($data);
  718. $this->writeAmf3Int($obj_length << 1 | 0x01);
  719. $this->outBuffer .= $data;
  720. }
  721. }
  722. /**
  723. * looks if $obj already has a reference. If it does, write it, and return true. If not, add it to the references array.
  724. * Depending on whether or not the spl_object_hash function can be used ( available (PHP >= 5.2), and can only be used on an object)
  725. * things are handled a bit differently:
  726. * - if possible, objects are hashed and the hash is used as a key to the references array. So the array has the structure hash => reference
  727. * - if not, the object is pushed to the references array, and array_search is used. So the array has the structure reference => object.
  728. * maxing out the number of stored references improves performance(tested with an array of 9000 identical objects). This may be because isset's performance
  729. * is linked to the size of the array. weird...
  730. * note on using $references[$count] = &$obj; rather than
  731. * $references[] = &$obj;
  732. * the first one is right, the second is not, as with the second one we could end up with the following:
  733. * some object hash => 0, 0 => array. (it should be 1 => array)
  734. *
  735. * This also means that 2 completely separate instances of a class but with the same values will be written fully twice if we can't use the hash system
  736. *
  737. * @param mixed $obj
  738. * @param array $references
  739. */
  740. protected function handleReference(&$obj, array &$references) {
  741. $key = false;
  742. $count = count($references);
  743. if (is_object($obj) && function_exists('spl_object_hash')) {
  744. $hash = spl_object_hash($obj);
  745. if (isset($references[$hash])) {
  746. $key = $references[$hash];
  747. } else {
  748. if ($count <= self::MAX_STORED_OBJECTS) {
  749. //there is some space left, store object for reference
  750. $references[$hash] = $count;
  751. }
  752. }
  753. } else {
  754. //no hash available, use array with simple numeric keys
  755. $key = array_search($obj, $references, TRUE);
  756. if (($key === false) && ($count <= self::MAX_STORED_OBJECTS)) {
  757. // $key === false means the object isn't already stored
  758. // count... means there is still space
  759. //so only store if these 2 conditions are met
  760. $references[$count] = &$obj;
  761. }
  762. }
  763. if ($key !== false) {
  764. //reference exists. write it and return true
  765. if ($this->packet->amfVersion == Amfphp_Core_Amf_Constants::AMF0_ENCODING) {
  766. $this->writeReference($key);
  767. } else {
  768. $handle = $key << 1;
  769. $this->writeAmf3Int($handle);
  770. }
  771. return true;
  772. } else {
  773. return false;
  774. }
  775. }
  776. /**
  777. * writes a typed object. Type is determined by having an "explicit type" field. If this field is
  778. * not set, call writeAmf3AnonymousObject
  779. * write all properties as sealed members.
  780. * @param object $d
  781. */
  782. protected function writeAmf3TypedObject($d) {
  783. //Write the object tag
  784. $this->outBuffer .= "\12";
  785. if ($this->handleReference($d, $this->storedObjects)) {
  786. return;
  787. }
  788. $explicitTypeField = Amfphp_Core_Amf_Constants::FIELD_EXPLICIT_TYPE;
  789. $className = $d->$explicitTypeField;
  790. $propertyNames = null;
  791. if (isset($this->className2TraitsInfo[$className])) {
  792. //we have traits information and a reference for it, so use a traits reference
  793. $traitsInfo = $this->className2TraitsInfo[$className];
  794. $propertyNames = $traitsInfo['propertyNames'];
  795. $referenceId = $traitsInfo['referenceId'];
  796. $traitsReference = $referenceId << 2 | 1;
  797. $this->writeAmf3Int($traitsReference);
  798. } else {
  799. //no available traits information. Write the traits
  800. $propertyNames = array();
  801. foreach ($d as $key => $value) {
  802. if ($key[0] != "\0" && $key != $explicitTypeField) { //Don't write protected properties or explicit type
  803. $propertyNames[] = $key;
  804. }
  805. }
  806. //U29O-traits: 0011 in LSBs, and number of properties
  807. $numProperties = count($propertyNames);
  808. $traits = $numProperties << 4 | 3;
  809. $this->writeAmf3Int($traits);
  810. //class name
  811. $this->writeAmf3String($className);
  812. //list of property names
  813. foreach ($propertyNames as $propertyName) {
  814. $this->writeAmf3String($propertyName);
  815. }
  816. //save for reference
  817. $traitsInfo = array('referenceId' => count($this->className2TraitsInfo), 'propertyNames' => $propertyNames);
  818. $this->className2TraitsInfo[$className] = $traitsInfo;
  819. }
  820. //list of values
  821. foreach ($propertyNames as $propertyName) {
  822. $this->writeAmf3Data($d->$propertyName);
  823. }
  824. }
  825. }
  826. ?>