<?php /** * AMFDeserializer takes the raw amf input stream and converts it PHP objects * representing the data. * * @license http://opensource.org/licenses/gpl-license.php GNU Public License * @copyright (c) 2003 amfphp.org * @package flashservices * @subpackage io * @version $Id$ */ /** * Required classes */ require_once(AMFPHP_BASE . "shared/util/MessageBody.php"); require_once(AMFPHP_BASE . "shared/util/MessageHeader.php"); require_once(AMFPHP_BASE . "amf/util/DateWrapper.php"); class AMFBaseDeserializer { /** * The raw data input * * @access private * @var string */ var $raw_data; /** * The current seek cursor of the stream * * @access private * @var int */ var $current_byte; /** * The length of the stream. Since this class is not actually using a stream * the entire content of the stream is passed in as the initial argument so the * length can be determined. * * @access private * @var int */ var $content_length; /** * The number of headers in the packet. * * @access private * @var int */ var $header_count; /** * The content of the packet headers * * @access private * @var string */ var $headers; /** * The number of bodys in the packet. * * @access private * @var int */ var $body_count; /** * The content of the body elements * * @access private * @var string */ var $body; /** * The object to store the amf data. * * @access private * @var object */ var $amfdata; /** * The instance of the amfinput stream object * * @access private * @var object */ var $inputStream; /** * metaInfo */ var $meta; var $storedStrings; var $storedObjects; var $storedDefinitions; var $amf0storedObjects; var $native; /** * Constructor method for the deserializer. Constructing the deserializer converts the input stream * content to a AMFObject. * * @param object $is The referenced input stream */ function AMFBaseDeserializer($rd) { $this->isBigEndian = AMFPHP_BIG_ENDIAN; $this->current_byte = 0; $this->raw_data = $rd; // store the stream in this object $this->content_length = strlen($this->raw_data); // grab the total length of this stream $this->charsetHandler = new CharsetHandler('flashtophp'); $this->storedStrings = array(); $this->storedObjects = array(); $this->storedDefinitions = array(); $this->native = $GLOBALS['amfphp']['native'] && function_exists('amf_decode'); $this->decodeFlags = (AMFPHP_BIG_ENDIAN*2) | 4; } /** * deserialize invokes this class to transform the raw data into valid object * * @param object $amfdata The object to put the deserialized data in */ function deserialize (&$amfdata) { $time = microtime_float(); $this->amfdata = &$amfdata; $this->readHeader(); // read the binary header $this->readBody(); // read the binary body if($this->decodeFlags & 1 == 1) { //AMF3 mode $GLOBALS['amfphp']['encoding'] = "amf3"; } global $amfphp; $amfphp['decodeTime'] = microtime_float() - $time; } /** * returns the built AMFObject from the deserialization operation * * @return object The deserialized AMFObject */ function getAMFObject() { return $this->amfdata; } /** * Decode callback is triggered when an object is encountered on decode */ function decodeCallback($event, $arg) { if($event == 1) //Object { $type =$arg; return $this->mapClass($type); } else if($event == 2) //Object post decode { $obj = $arg; if(method_exists($obj, 'init')) { $obj->init(); } return $obj; } else if($event == 3) //XML post-decode { return $arg; } else if($event == 4) //Serializable post-decode { if($type == 'flex.messaging.io.ArrayCollection' || $type == 'flex.messaging.io.ObjectProxy') { return; } else { trigger_error("Unable to read externalizable data type " . $type, E_USER_ERROR); return "error"; } } else if($event == 5) //ByteArray post decode { return new ByteArray($arg); } } /** * readHeader converts that header section of the amf message into php obects. * Header information typically contains meta data about the message. */ function readHeader() { $topByte = $this->readByte(); // ignore the first two bytes -- version or something $secondByte = $this->readByte(); //0 for Flash, //1 for FlashComm //Disable debug events for FlashComm $GLOBALS['amfphp']['isFlashComm'] = $secondByte == 1; //If firstByte != 0, then the AMF data is corrupted, for example the transmission // if(!($topByte == 0 || $topByte == 3)) { trigger_error("Malformed AMF message, connection may have dropped"); exit(); } $this->header_count = $this->readInt(); // find the total number of header elements while ($this->header_count--) { // loop over all of the header elements $name = $this->readUTF(); $required = $this->readByte() == 1; // find the must understand flag //$length = $this->readLong(); // grab the length of the header element $this->current_byte += 4; // grab the length of the header element if($this->native) { $content = amf_decode($this->raw_data, $this->decodeFlags, $this->current_byte, array(& $this, "decodeCallback")); } else { $type = $this->readByte(); // grab the type of the element $content = $this->readData($type); // turn the element into real data } $this->amfdata->addHeader(new MessageHeader($name, $required, $content)); // save the name/value into the headers array } } /** * readBody converts the payload of the message into php objects. */ function readBody() { $this->body_count = $this->readInt(); // find the total number of body elements while ($this->body_count--) { // loop over all of the body elements $this->amf0storedObjects = array(); $this->storedStrings = array(); $this->storedObjects = array(); $this->storedDefinitions = array(); $target = $this->readUTF(); $response = $this->readUTF(); // the response that the client understands //$length = $this->readLong(); // grab the length of the body element $this->current_byte += 4; if($this->native) $data = amf_decode($this->raw_data, $this->decodeFlags, $this->current_byte, array(& $this, "decodeCallback")); else { $type = $this->readByte(); // grab the type of the element $data = $this->readData($type); // turn the element into real data } $this->amfdata->addBody(new MessageBody($target, $response, $data)); // add the body element to the body object } } /******************************************************************************** * This used to be in AmfInputStream ******************************************************************************** /** * readByte grabs the next byte from the data stream and returns it. * * @return int The next byte converted into an integer */ function readByte() { return ord($this->raw_data[$this->current_byte++]); // return the next byte } /** * readInt grabs the next 2 bytes and returns the next two bytes, shifted and combined * to produce the resulting integer * * @return int The resulting integer from the next 2 bytes */ function readInt() { return ((ord($this->raw_data[$this->current_byte++]) << 8) | ord($this->raw_data[$this->current_byte++])); // read the next 2 bytes, shift and add } /** * readUTF first grabs the next 2 bytes which represent the string length. * Then it grabs the next (len) bytes of the resulting string. * * @return string The utf8 decoded string */ function readUTF() { $length = $this->readInt(); // get the length of the string (1st 2 bytes) //BUg fix:: if string is empty skip ahead if($length == 0) { return ""; } else { $val = substr($this->raw_data, $this->current_byte, $length); // grab the string $this->current_byte += $length; // move the seek head to the end of the string return $this->charsetHandler->transliterate($val); // return the string } } /** * readLong grabs the next 4 bytes shifts and combines them to produce an integer * * @return int The resulting integer from the next 4 bytes */ function readLong() { return ((ord($this->raw_data[$this->current_byte++]) << 24) | (ord($this->raw_data[$this->current_byte++]) << 16) | (ord($this->raw_data[$this->current_byte++]) << 8) | ord($this->raw_data[$this->current_byte++])); // read the next 4 bytes, shift and add } /** * readDouble reads the floating point value from the bytes stream and properly orders * the bytes depending on the system architecture. * * @return float The floating point value of the next 8 bytes */ function readDouble() { $bytes = substr($this->raw_data, $this->current_byte, 8); $this->current_byte += 8; if ($this->isBigEndian) { $bytes = strrev($bytes); } $zz = unpack("dflt", $bytes); // unpack the bytes return $zz['flt']; // return the number from the associative array } /** * readLongUTF first grabs the next 4 bytes which represent the string length. * Then it grabs the next (len) bytes of the resulting in the string * * @return string The utf8 decoded string */ function readLongUTF() { $length = $this->readLong(); // get the length of the string (1st 4 bytes) $val = substr($this->raw_data, $this->current_byte, $length); // grab the string $this->current_byte += $length; // move the seek head to the end of the string return $this->charsetHandler->transliterate($val); // return the string } function mapClass($typeIdentifier) { //Check out if class exists if($typeIdentifier == "") { return NULL; } $clazz = NULL; $mappedClass = str_replace('.', '/', $typeIdentifier); if($typeIdentifier == "flex.messaging.messages.CommandMessage") { return new CommandMessage(); } if($typeIdentifier == "flex.messaging.messages.RemotingMessage") { return new RemotingMessage(); } if(isset($GLOBALS['amfphp']['incomingClassMappings'][$typeIdentifier])) { $mappedClass = str_replace('.', '/', $GLOBALS['amfphp']['incomingClassMappings'][$typeIdentifier]); } $include = FALSE; if(file_exists($GLOBALS['amfphp']['customMappingsPath'] . $mappedClass . '.php')) { $include = $GLOBALS['amfphp']['customMappingsPath'] . $mappedClass . '.php'; } elseif(file_exists($GLOBALS['amfphp']['customMappingsPath'] . $mappedClass . '.class.php')) { $include = $GLOBALS['amfphp']['customMappingsPath'] . $mappedClass . '.class.php'; } if($include !== FALSE) { include_once($include); $lastPlace = strrpos('/' . $mappedClass, '/'); $classname = substr($mappedClass, $lastPlace); if(class_exists($classname)) { $clazz = new $classname; } } return $clazz; // return the object } } ?>