<?php
/*
  this software by Musicman 2003
  is covered by the GPL
  for the exact terms and conditions please see http://www.fsf.org
  http://www.fontimages.org.uk/flash/struc.html
  
  revised Richard Fairhurst 2006:
    - now copes with 'grouped' requests (i.e. when the Flash client batches
      a whole load of NetConnection.calls in one call to the AMF server)
    - will send empty arrays
    - understands mixedArrays
    - code tidied

  usage:
  
  $rqst->fn						name of service requested
  $rqst->hdrs					headers sent by Flash client
  $rqst->fnargs					arguments sent by Flash client
  								- array of arrays, one for every AMF request,
  								  each containing the arguments sent by
  								  that request
  $rqst->addresult(num,data)	give a response back to the Flash client
  								- num is the number of the AMF request to
  								  which we're responding
  								- data is the actual response
  $rqst->send					send all responses prepared by addresult

  example:
    
    $rqst=new amfdata();
    switch ($rqst->fn) {		# what service are we requesting?
      case 'senddata':
      	foreach ($rqst->fnargs as $seq=>$args) {
      	  print "AMF request $seq, first argument is ".$args[0]."\n";
      	  $rqst->addresult($seq,"Received ok.");
      	}
      	$rqst->send();
      	break;
    }

*/


class amfdata {
	var $fn, $fnargs;
	var $hdrs;
	var $byteorder;
	var $results, $result;

	# =====	amfdata
	#		create and parse a new AMF request

	function amfdata() {
		$this->hdrs=array();
		$this->fnargs=array();
		$this->results=0;
		$this->result='';

		$tmp=pack("d", 1);
		if	    ($tmp == "\0\0\0\0\0\0\360\77") { $this->byteorder='x86'; }
		else if ($tmp == "\77\360\0\0\0\0\0\0")	{ $this->byteorder='ppc'; }
		else die("unknown byteorder in amfdata\n");

		# ---	Get raw data
		$d=$GLOBALS['HTTP_RAW_POST_DATA'];
		$l=strlen($d);
		$n=3;

		# ---	Read headers
		$nv=ord($d[$n++]);
		while(--$nv >= 0) {
			$key=$this->getstr($d, $n);
			$n++;
			$lo=$this->getlength($d, $n);	#Ênot used
			$ch=ord($d[$n++]);
			$val=$this->parseitem($ch, $d, $n);
			$this->$hdrs[$key]=$val;
		}

		# ---	Read each call
		$n+=2;
		while ($n<$l) {

			#	Get call name
			$this->fn=$this->getstr($d, $n);

			#	Get number in sequence
			$seq=substr($this->getstr($d, $n),1);
			$lo=$this->getlength($d, $n);	# length of all params? not used

			#	Get all parameters (sent as an array, hence the '10')
			$ch=ord($d[$n++]); if($ch != 10) { print " ??";	}	# error
			$lo=$this->getlength($d, $n);
			for ($ni=0; $ni<$lo; $ni++) {
				$ch=ord($d[$n++]);
				$p=$this->parseitem($ch, $d, $n);
				$this->fnargs[$seq][]=$p;
			}
		}
		return $this;
	}


	# =====	addresult
	#		add an AMF response
	
	function addresult($seq,$data) {
		$this->results++;
		$this->result.=$this->sendstr("/$seq/onResult").
					   $this->sendstr("null").
					   pack("N",-1).
					   $this->sendobj($data);
	}


	# =====	send
	# 		send all AMF responses
	
	function send() {
		header('Content-type: application/x-amf');
		print "\0\0\0\0";
		print pack("n",$this->results);
		print $this->result;
	}



	# ========================================================================
	# Get/send data types

	# =====	Item parser (item type, raw form data, position in data)
	#		this reads the ID byte and calls the appropriate 'get' routine

	function parseitem($ch, $d, &$n) {
		switch($ch) {
			case 0: return $this->getnumber($d,$n); break;	// number
			case 1: $ch= ord($d[$n++]); return $ch; break;	// boolean
			case 2:	return $this->getstr($d, $n);	break;	// string
			case 3: return $this->getobj($d, $n);	break;	// object
			//   4 unsupported								// movieclip
			case 5: return null;							// null
			case 6: return null;							// undefined
			case 8: return $this->getmixed($d, $n);	break;	// mixedArray
			case 10:return $this->getarray($d, $n); break;	// array
			default:print "xxx $ch ";						// error
		}
	}

	# =====	Get each data type
	#		all called with $d (raw form data), $n (position in data)

	function getstr($d, &$n) {       
		$hi=ord($d[$n++]);
		$lo=ord($d[$n++]);
		$lo+=256*$hi;
		$val=substr($d,$n,$lo);
		$n+=$lo;
		return $val;
	}

	function getnumber($d, &$n) {       
		$ibf="";
		switch($this->byteorder) {
			case 'x86': for ($nc=7; $nc>=0; $nc--) { $ibf.=$d[$n+$nc]; }
						break;
			case 'ppc':	$ibf=substr($d,$n,8);
						break;
		}
		$n += 8;
		$zz=unpack("dflt", $ibf);
		return $zz['flt'];
	}

	function getobj($d, &$n) {
		$ret=array();
		while($key=$this->getstr($d, $n)) {
			$ch=ord($d[$n++]);
			$val=$this->parseitem($ch, $d, $n);
			$ret[$key]=$val;
		}
		$ch=ord($d[$n++]);
		if($ch != 9) { print "obj?? $ch "; }	# error
		return $ret;
	}

	function getmixed($d, &$n) {
		$lo=$this->getlength($d, $n);	# not used
		return $this->getobj($d,$n);
	}
        		
	function getarray($d, &$n) {
		$ret=array();
		$lo=$this->getlength($d, $n);
		# read array
		for ($ni=0; $ni<$lo; $ni++) {
			$ch=ord($d[$n++]);
			$ret[]=$this->parseitem($ch, $d, $n);
		}
		return $ret;
	}

	# =====	Send all data types

	function sendobj($val) {
		if(is_array($val) || is_object($val)) {
			$first=1;
			$num_array=1;
			foreach($val as $key => $data) {
				if(!is_int($key) || ($key < 0)) {
					$num_array=0;
					break;
				}
				if		($first)   { $lo=$hi=$key; $first=0; }
				else if ($key<$lo) { $lo=$key; }
				else if ($key>$hi) { $hi=$key; }
			}
			if ($num_array) {       
				# Send as array (code 10)
				if (!empty($hi)) {
					$ret="\12" . pack("N", $hi+1);
					for($n=0; $n<=$hi; $n++) {
						$ret.=$this->sendobj($val[$n]);
					}
				} else {
					$ret="\12".pack("N",0);
				}
			} else {
				# Send as object (code 3)
				$ret="\3";
				foreach ($val as $key => $data) {
					$ret.=$this->sendstr($key);
					$ret.=$this->sendobj($data);
				}
				$ret .= $this->sendstr('') . "\11";
			}
			return $ret;
		} else if (is_integer($val) || is_numeric($val)) {
			# Send as number (code 0)
			return "\0" . $this->sendnum($val);
		} else {
			# Send as string (code 2)
			return "\2" . $this->sendstr($val);
		}
	}

	# =====	Utility functions

	function sendstr($str) { return pack("n", strlen($str)).$str; }

	function sendnum($val) {
		$b=pack("d", $val);
		switch($this->byteorder) {
			case 'x86': $r="";
						for ($n=7; $n>=0; $n--) { $r.=$b[$n]; }
						return $r;
			case 'ppc':	return $b;
		}
	}
	
	function getlength($d,&$n) {
		for ($b=0, $c=0; $c<4; $c++) {
			$b*=256;
			$b+=ord($d[$n++]);
		}
		return $b;
	}

}
