Serializer.php 35 KB

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