<?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$
*/
include_once(AMFPHP_BASE . "amf/io/AMFBaseDeserializer.php");
class AMFDeserializer extends 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 AMFDeserializer($rd) {
AMFBaseDeserializer::AMFBaseDeserializer($rd);
}
/**
* readObject reads the name/value properties of the amf message and converts them into
* their equivilent php representation
*
* @return array The php array with the object data
*/
function readObject() {
$ret = array(); // init the array
$this->amf0storedObjects[] = & $ret;
$key = $this->readUTF(); // grab the key
for ($type = $this->readByte(); $type != 9; $type = $this->readByte()) {
$val = $this->readData($type); // grab the value
$ret[$key] = $val; // save the name/value pair in the array
$key = $this->readUTF(); // get the next name
}
return $ret; // return the array
}
/**
* readMixedObject reads the name/value properties of the amf message and converts
* numeric looking keys to numeric keys
*
* @return array The php array with the object data
*/
function readMixedObject() {
$ret = array(); // init the array
$this->amf0storedObjects[] = & $ret;
$key = $this->readUTF(); // grab the key
for ($type = $this->readByte(); $type != 9; $type = $this->readByte()) {
$val = $this->readData($type); // grab the value
if(is_numeric($key))
{
$key = (float) $key;
}
$ret[$key] = $val; // save the name/value pair in the array
$key = $this->readUTF(); // get the next name
}
return $ret; // return the array
}
/**
* readArray turns an all numeric keyed actionscript array into a php array.
*
* @return array The php array
*/
function readArray() {
$ret = array(); // init the array object
$this->amf0storedObjects[] = & $ret;
$length = $this->readLong(); // get the length of the array
for ($i = 0; $i < $length; $i++) { // loop over all of the elements in the data
$type = $this->readByte(); // grab the type for each element
$ret[] = $this->readData($type); // grab each element
}
return $ret; // return the data
}
/**
* readMixedArray turns an array with numeric and string indexes into a php array
*
* @return array The php array with mixed indexes
*/
function readMixedArray() {
//$length = $this->readLong(); // get the length property set by flash
$this->current_byte += 4;
return $this->readMixedObject(); // return the body of mixed array
}
/**
* readCustomClass reads the amf content associated with a class instance which was registered
* with Object.registerClass. In order to preserve the class name an additional property is assigned
* to the object "_explicitType". This property will be overwritten if it existed within the class already.
*
* @return object The php representation of the object
*/
function readCustomClass() {
$typeIdentifier = str_replace('..', '', $this->readUTF());
$obj = $this->mapClass($typeIdentifier);
$isObject = true;
if($obj == NULL)
{
$obj = array();
$isObject = false;
}
$this->amf0storedObjects[] = & $obj;
$key = $this->readUTF(); // grab the key
for ($type = $this->readByte(); $type != 9; $type = $this->readByte()) {
$val = $this->readData($type); // grab the value
if($isObject)
{
$obj->$key = $val; // save the name/value pair in the array
}
else
{
$obj[$key] = $val; // save the name/value pair in the array
}
$key = $this->readUTF(); // get the next name
}
if(!$isObject)
{
$obj['_explicitType'] = $typeIdentifier;
}
return $obj; // return the array
}
/**
* readDate reads a date from the amf message and returns the time in ms.
* This method is still under development.
*
* @return long The date in ms.
*/
function readDate() {
$ms = $this->readDouble(); // date in milliseconds from 01/01/1970
$int = $this->readInt(); // nasty way to get timezone
if ($int > 720) {
$int = - (65536 - $int);
}
$int *= -60;
//$int *= 1000;
//$min = $int % 60;
//$timezone = "GMT " . - $hr . ":" . abs($min);
// end nastiness
//We store the last timezone found in date fields in the request
//FOr most purposes, it's expected that the timezones
//don't change from one date object to the other (they change per client though)
DateWrapper::setTimezone($int);
return $ms;
}
/**
* readReference replaces the old readFlushedSO. It treats where there
* are references to other objects. Currently it does not resolve the
* object as this would involve a serious amount of overhead, unless
* you have a genius idea
*
* @return String
*/
function readReference() {
$reference = $this->readInt();
return $this->amf0storedObjects[$reference];
}
function readAny()
{
if($this->native)
return amfphp_decode($this->raw_data, $this->decodeFlags, $this->current_byte, array(& $this, "decodeCallback"));
else
{
$type = $this->readByte(); // grab the type of the element
return $this->readData($type); // turn the element into real data
}
}
/**
* readData is the main switch for mapping a type code to an actual
* implementation for deciphering it.
*
* @param mixed $type The $type integer
* @return mixed The php version of the data in the message block
*/
function readData($type) {
switch ($type) {
case 0: // number
$data = $this->readDouble();
break;
case 1: // boolean
$data = $this->readByte() == 1;
break;
case 2: // string
$data = $this->readUTF();
break;
case 3: // object Object
$data = $this->readObject();
break;
case 5: // null
$data = null;
break;
case 6: // undefined
$data = null;
break;
case 7: // Circular references are returned here
$data = $this->readReference();
break;
case 8: // mixed array with numeric and string keys
$data = $this->readMixedArray();
break;
case 10: // array
$data = $this->readArray();
break;
case 11: // date
$data = $this->readDate();
break;
case 12: // string, strlen(string) > 2^16
$data = $this->readLongUTF();
break;
case 13: // mainly internal AS objects
$data = null;
break;
case 15: // XML
$data = $this->readLongUTF();
break;
case 16: // Custom Class
$data = $this->readCustomClass();
break;
case 17: //AMF3-specific
$GLOBALS['amfphp']['encoding'] = "amf3";
$data = $this->readAmf3Data();
break;
default: // unknown case
trigger_error("Found unhandled type with code: $type");
exit();
break;
}
return $data;
}
/********************************************************************************
* This is the AMF3 specific stuff
********************************************************************************/
function readAmf3Data()
{
$type = $this->readByte();
switch($type)
{
case 0x00 : return null; //undefined
case 0x01 : return null; //null
case 0x02 : return false; //boolean false
case 0x03 : return true; //boolean true
case 0x04 : return $this->readAmf3Int();
case 0x05 : return $this->readDouble();
case 0x06 : return $this->readAmf3String();
case 0x07 : return $this->readAmf3XmlString();
case 0x08 : return $this->readAmf3Date();
case 0x09 : return $this->readAmf3Array();
case 0x0A : return $this->readAmf3Object();
case 0x0B : return $this->readAmf3XmlString();
case 0x0C : return $this->readAmf3ByteArray();
default: trigger_error("undefined Amf3 type encountered: " . $type, E_USER_ERROR);
}
}
/// <summary>
/// Handle decoding of the variable-length representation
/// which gives seven bits of value per serialized byte by using the high-order bit
/// of each byte as a continuation flag.
/// </summary>
/// <returns></returns>
function readAmf3Int()
{
$int = $this->readByte();
if($int < 128)
return $int;
else
{
$int = ($int & 0x7f) << 7;
$tmp = $this->readByte();
if($tmp < 128)
{
return $int | $tmp;
}
else
{
$int = ($int | ($tmp & 0x7f)) << 7;
$tmp = $this->readByte();
if($tmp < 128)
{
return $int | $tmp;
}
else
{
$int = ($int | ($tmp & 0x7f)) << 8;
$tmp = $this->readByte();
$int |= $tmp;
// Check if the integer should be negative
if (($int & 0x10000000) != 0) {
// and extend the sign bit
$int |= 0xe0000000;
}
return $int;
}
}
}
}
function readAmf3Date()
{
$dateref = $this->readAmf3Int();
if (($dateref & 0x01) == 0) {
$dateref = $dateref >> 1;
if ($dateref>=count($this->storedObjects)) {
trigger_error('Undefined date reference: ' . $dateref, E_USER_ERROR);
return false;
}
return $this->storedObjects[$dateref];
}
//$timeOffset = ($dateref >> 1) * 6000 * -1;
$ms = $this->readDouble();
//$date = $ms-$timeOffset;
$date = $ms;
$this->storedObjects[] = & $date;
return $date;
}
/**
* readString
*
* @return string
*/
function readAmf3String() {
$strref = $this->readAmf3Int();
if (($strref & 0x01) == 0) {
$strref = $strref >> 1;
if ($strref >= count($this->storedStrings)) {
trigger_error('Undefined string reference: ' . $strref, E_USER_ERROR);
return false;
}
return $this->storedStrings[$strref];
} else {
$strlen = $strref >> 1;
$str = "";
if ($strlen > 0)
{
$str = $this->readBuffer($strlen);
$this->storedStrings[] = $str;
}
return $str;
}
}
function readAmf3XmlString()
{
$handle = $this->readAmf3Int();
$inline = (($handle & 1) != 0 ); $handle = $handle >> 1;
if( $inline )
{
$xml = $this->readBuffer($handle);
$this->storedStrings[] = $xml;
}
else
{
$xml = $this->storedObjects[$handle];
}
return $xml;
}
function readAmf3ByteArray()
{
$handle = $this->readAmf3Int();
$inline = (($handle & 1) != 0 );$handle = $handle >> 1;
if( $inline )
{
$ba = new ByteArray($this->readBuffer($handle));
$this->storedObjects[] = $ba;
}
else
{
$ba = $this->storedObjects[$handle];
}
return $ba;
}
function readAmf3Array()
{
$handle = $this->readAmf3Int();
$inline = (($handle & 1) != 0 ); $handle = $handle >> 1;
if( $inline )
{
$hashtable = array();
$this->storedObjects[] = & $hashtable;
$key = $this->readAmf3String();
while( $key != "" )
{
$value = $this->readAmf3Data();
$hashtable[$key] = $value;
$key = $this->readAmf3String();
}
for($i = 0; $i < $handle; $i++)
{
//Grab the type for each element.
$value = $this->readAmf3Data();
$hashtable[$i] = $value;
}
return $hashtable;
}
else
{
return $this->storedObjects[$handle];
}
}
function readAmf3Object()
{
$handle = $this->readAmf3Int();
$inline = (($handle & 1) != 0 ); $handle = $handle >> 1;
if( $inline )
{
//an inline object
$inlineClassDef = (($handle & 1) != 0 );$handle = $handle >> 1;
if( $inlineClassDef )
{
//inline class-def
$typeIdentifier = $this->readAmf3String();
$typedObject = !is_null($typeIdentifier) && $typeIdentifier != "";
//flags that identify the way the object is serialized/deserialized
$externalizable = (($handle & 1) != 0 );$handle = $handle >> 1;
$dynamic = (($handle & 1) != 0 );$handle = $handle >> 1;
$classMemberCount = $handle;
$classMemberDefinitions = array();
for($i = 0; $i < $classMemberCount; $i++)
{
$classMemberDefinitions[] = $this->readAmf3String();
}
//string mappedTypeName = typeIdentifier;
//if( applicationContext != null )
// mappedTypeName = applicationContext.GetMappedTypeName(typeIdentifier);
$classDefinition = array("type" => $typeIdentifier, "members" => $classMemberDefinitions,
"externalizable" => $externalizable, "dynamic" => $dynamic);
$this->storedDefinitions[] = $classDefinition;
}
else
{
//a reference to a previously passed class-def
$classDefinition = $this->storedDefinitions[$handle];
}
}
else
{
//an object reference
return $this->storedObjects[$handle];
}
$type = $classDefinition['type'];
$obj = $this->mapClass($type);
$isObject = true;
if($obj == NULL)
{
$obj = array();
$isObject = false;
}
//Add to references as circular references may search for this object
$this->storedObjects[] = & $obj;
if( $classDefinition['externalizable'] )
{
if($type == 'flex.messaging.io.ArrayCollection')
{
$obj = $this->readAmf3Data();
}
else if($type == 'flex.messaging.io.ObjectProxy')
{
$obj = $this->readAmf3Data();
}
else
{
trigger_error("Unable to read externalizable data type " . $type, E_USER_ERROR);
}
}
else
{
$members = $classDefinition['members'];
$memberCount = count($members);
for($i = 0; $i < $memberCount; $i++)
{
$val = $this->readAmf3Data();
$key = $members[$i];
if($isObject)
{
$obj->$key = $val;
}
else
{
$obj[$key] = $val;
}
}
if($classDefinition['dynamic']/* && obj is ASObject*/)
{
$key = $this->readAmf3String();
while( $key != "" )
{
$value = $this->readAmf3Data();
if($isObject)
{
$obj->$key = $value;
}
else
{
$obj[$key] = $value;
}
$key = $this->readAmf3String();
}
}
if($type != '' && !$isObject)
{
$obj['_explicitType'] = $type;
}
}
if($isObject && method_exists($obj, 'init'))
{
$obj->init();
}
return $obj;
}
/**
* Taken from SabreAMF
*/
function readBuffer($len)
{
$data = substr($this->raw_data,$this->current_byte,$len);
$this->current_byte += $len;
return $data;
}
}
?>