472 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			472 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
/**
 | 
						|
 * ManiaPlanet dedicated server Xml-RPC client
 | 
						|
 *
 | 
						|
 * @license     http://www.gnu.org/licenses/lgpl.html LGPL License 3
 | 
						|
 */
 | 
						|
 | 
						|
namespace Maniaplanet\DedicatedServer\Xmlrpc;
 | 
						|
 | 
						|
if (!defined('LF'))
 | 
						|
{
 | 
						|
	define('LF', "\n");
 | 
						|
}
 | 
						|
 | 
						|
if (!defined('SIZE_MAX'))
 | 
						|
{
 | 
						|
	define('SIZE_MAX', 4096*1024);
 | 
						|
}
 | 
						|
 | 
						|
class Client 
 | 
						|
{
 | 
						|
	public $socket;
 | 
						|
	public $message = false;
 | 
						|
	public $cb_message = array();
 | 
						|
	public $reqhandle;
 | 
						|
	public $protocol = 0;
 | 
						|
 | 
						|
	static $received;
 | 
						|
	static $sent;
 | 
						|
	
 | 
						|
	function bigEndianTest() 
 | 
						|
	{
 | 
						|
		list($endiantest) = array_values(unpack('L1L', pack('V', 1)));
 | 
						|
		if ($endiantest != 1) 
 | 
						|
		{
 | 
						|
			if(!function_exists(__NAMESPACE__.'\\unpack'))
 | 
						|
			{
 | 
						|
				/**
 | 
						|
				 * The following code is a workaround for php's unpack function which
 | 
						|
				 * does not have the capability of unpacking double precision floats
 | 
						|
				 * that were packed in the opposite byte order of the current machine.
 | 
						|
				 */
 | 
						|
				function unpack($format, $data) 
 | 
						|
				{
 | 
						|
					$ar = unpack($format, $data);
 | 
						|
					$vals = array_values($ar);
 | 
						|
					$f = explode('/', $format);
 | 
						|
					$i = 0;
 | 
						|
					foreach ($f as $f_k => $f_v) 
 | 
						|
					{
 | 
						|
						$repeater = intval(substr($f_v, 1));
 | 
						|
						if ($repeater == 0)
 | 
						|
						{
 | 
						|
							$repeater = 1;
 | 
						|
						}
 | 
						|
						if ($f_v{1} == '*') 
 | 
						|
						{
 | 
						|
							$repeater = count($ar) - $i;
 | 
						|
						}
 | 
						|
						if ($f_v{0} != 'd') 
 | 
						|
						{
 | 
						|
							$i += $repeater;
 | 
						|
							continue;
 | 
						|
						}
 | 
						|
						$j = $i + $repeater;
 | 
						|
						for ($a = $i; $a < $j; ++$a) 
 | 
						|
						{
 | 
						|
							$p = pack('d', $vals[$i]);
 | 
						|
							$p = strrev($p);
 | 
						|
							list($vals[$i]) = array_values(unpack('d1d', $p));
 | 
						|
							++$i;
 | 
						|
						}
 | 
						|
					}
 | 
						|
					$a = 0;
 | 
						|
					foreach ($ar as $ar_k => $ar_v) 
 | 
						|
					{
 | 
						|
						$ar[$ar_k] = $vals[$a];
 | 
						|
						++$a;
 | 
						|
					}
 | 
						|
					return $ar;
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	function __construct($hostname = 'localhost', $port = 5000, $timeout) 
 | 
						|
	{
 | 
						|
		$this->socket = false;
 | 
						|
		$this->reqhandle = 0x80000000;
 | 
						|
		$this->init($hostname, $port, $timeout);
 | 
						|
	}
 | 
						|
	
 | 
						|
	function __destruct()
 | 
						|
	{
 | 
						|
		$this->terminate();
 | 
						|
	}
 | 
						|
 | 
						|
	protected function init($hostname, $port, $timeout) 
 | 
						|
	{
 | 
						|
 | 
						|
		$this->bigEndianTest();
 | 
						|
 | 
						|
		// open connection
 | 
						|
		$this->socket = @fsockopen($hostname, $port, $errno, $errstr, $timeout);
 | 
						|
		if (!$this->socket) 
 | 
						|
		{
 | 
						|
			throw new Exception("transport error - could not open socket (error: $errno, $errstr)", -32300);
 | 
						|
		}
 | 
						|
		// handshake
 | 
						|
		$array_result = unpack('Vsize', fread($this->socket, 4));
 | 
						|
		$size = $array_result['size'];
 | 
						|
		if ($size > 64) 
 | 
						|
		{
 | 
						|
			throw new Exception('transport error - wrong lowlevel protocol header', -32300);
 | 
						|
		}
 | 
						|
		$handshake = fread($this->socket, $size);
 | 
						|
		if ($handshake == 'GBXRemote 1') 
 | 
						|
		{
 | 
						|
			$this->protocol = 1;
 | 
						|
		} 
 | 
						|
		elseif ($handshake == 'GBXRemote 2') 
 | 
						|
		{
 | 
						|
			$this->protocol = 2;
 | 
						|
		} 
 | 
						|
		else 
 | 
						|
		{
 | 
						|
			throw new Exception('transport error - wrong lowlevel protocol version', -32300);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	
 | 
						|
	function terminate() 
 | 
						|
	{
 | 
						|
		if ($this->socket) 
 | 
						|
		{
 | 
						|
			fclose($this->socket);
 | 
						|
			$this->socket = false;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	protected function sendRequest(Request $request) 
 | 
						|
	{
 | 
						|
		$xml = $request->getXml();
 | 
						|
 | 
						|
		@stream_set_timeout($this->socket, 20);  // timeout 20 s (to write the request)
 | 
						|
		// send request
 | 
						|
		$this->reqhandle++;
 | 
						|
		if ($this->protocol == 1) 
 | 
						|
		{
 | 
						|
			$bytes = pack('Va*', strlen($xml), $xml);
 | 
						|
		} 
 | 
						|
		else 
 | 
						|
		{
 | 
						|
			$bytes = pack('VVa*', strlen($xml), $this->reqhandle, $xml);
 | 
						|
		}
 | 
						|
 | 
						|
		$bytes_to_write = strlen($bytes);
 | 
						|
		
 | 
						|
		// increase sent counter ...
 | 
						|
		self::$sent += $bytes_to_write;
 | 
						|
		
 | 
						|
		while ($bytes_to_write > 0) 
 | 
						|
		{
 | 
						|
			$r = fwrite($this->socket, $bytes);
 | 
						|
			if ($r === false || $r == 0) 
 | 
						|
			{
 | 
						|
				throw new Exception('Connection interupted');
 | 
						|
			}
 | 
						|
 | 
						|
			$bytes_to_write -= $r;
 | 
						|
			if ($bytes_to_write == 0)
 | 
						|
			{
 | 
						|
				break;
 | 
						|
			}
 | 
						|
 | 
						|
			$bytes = substr($bytes, $r);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	protected function getResult() 
 | 
						|
	{
 | 
						|
		$contents = '';
 | 
						|
		$contents_length = 0;
 | 
						|
		do 
 | 
						|
		{
 | 
						|
			$size = 0;
 | 
						|
			$recvhandle = 0;
 | 
						|
			@stream_set_timeout($this->socket, 5);  // timeout 20 s (to read the reply header)
 | 
						|
			// Get result
 | 
						|
			if ($this->protocol == 1) 
 | 
						|
			{
 | 
						|
				$contents = fread($this->socket, 4);
 | 
						|
				if (strlen($contents) == 0) 
 | 
						|
				{
 | 
						|
					throw new Exception('transport error - connection interrupted!', -32700);
 | 
						|
				}
 | 
						|
				$array_result = unpack('Vsize', $contents);
 | 
						|
				$size = $array_result['size'];
 | 
						|
				$recvhandle = $this->reqhandle;
 | 
						|
			} 
 | 
						|
			else 
 | 
						|
			{
 | 
						|
				$contents = fread($this->socket, 8);
 | 
						|
				if (strlen($contents) == 0) 
 | 
						|
				{
 | 
						|
					throw new Exception('transport error - connection interrupted!', -32700);
 | 
						|
				}
 | 
						|
				$array_result = unpack('Vsize/Vhandle', $contents);
 | 
						|
				$size = $array_result['size'];
 | 
						|
				$recvhandle = $array_result['handle'];
 | 
						|
				// -- amd64 support --
 | 
						|
				$bits = sprintf('%b', $recvhandle);
 | 
						|
				if (strlen($bits) == 64) 
 | 
						|
				{
 | 
						|
					$recvhandle = bindec(substr($bits, 32));
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			if ($recvhandle == 0 || $size == 0) 
 | 
						|
			{
 | 
						|
				throw new Exception('transport error - connection interrupted!', -32700);
 | 
						|
			}
 | 
						|
			
 | 
						|
			if ($size > SIZE_MAX) 
 | 
						|
			{
 | 
						|
				throw new Exception("transport error - answer too big ($size)", -32700);
 | 
						|
			}
 | 
						|
 | 
						|
			self::$received += $size;
 | 
						|
			
 | 
						|
			$contents = '';
 | 
						|
			$contents_length = 0;
 | 
						|
			@stream_set_timeout($this->socket, 0, 10000);  // timeout 10 ms (for successive reads until end)
 | 
						|
			while ($contents_length < $size) 
 | 
						|
			{
 | 
						|
				$contents .= fread($this->socket, $size-$contents_length);
 | 
						|
				$contents_length = strlen($contents);
 | 
						|
			}
 | 
						|
 | 
						|
			if (($recvhandle & 0x80000000) == 0) 
 | 
						|
			{
 | 
						|
				// this is a callback, not our answer! handle= $recvhandle, xml-rpc= $contents
 | 
						|
				// just add it to the message list for the user to read
 | 
						|
				$new_cb_message = new Message($contents);
 | 
						|
				if ($new_cb_message->parse() && $new_cb_message->messageType != 'fault') 
 | 
						|
				{
 | 
						|
					array_push($this->cb_message, array($new_cb_message->methodName, $new_cb_message->params));
 | 
						|
				}
 | 
						|
			}
 | 
						|
		} 
 | 
						|
		while ((int)$recvhandle != (int)$this->reqhandle);
 | 
						|
 | 
						|
		$this->message = new Message($contents);
 | 
						|
		if (!$this->message->parse()) 
 | 
						|
		{
 | 
						|
			// XML error
 | 
						|
			throw new Exception('parse error. not well formed', -32700);
 | 
						|
		}
 | 
						|
		// Is the message a fault?
 | 
						|
		if ($this->message->messageType == 'fault') 
 | 
						|
		{
 | 
						|
			throw new Exception($this->message->faultString, $this->message->faultCode);
 | 
						|
		}
 | 
						|
		
 | 
						|
		return $this->message;
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
	function query() 
 | 
						|
	{
 | 
						|
		$args = func_get_args();
 | 
						|
		$method = array_shift($args);
 | 
						|
 | 
						|
		if (!$this->socket || $this->protocol == 0) 
 | 
						|
		{
 | 
						|
			throw new Exception('transport error - Client not initialized', -32300);
 | 
						|
		}
 | 
						|
 | 
						|
		$request = new Request($method, $args);
 | 
						|
 | 
						|
		// Check if request is larger than 1024 Kbytes
 | 
						|
		if ($request->getLength() > 1024*1024-8)
 | 
						|
		{
 | 
						|
			throw new Exception('transport error - request too large!', -32700);
 | 
						|
		}
 | 
						|
 | 
						|
		$this->sendRequest($request);
 | 
						|
		return $this->getResult();
 | 
						|
	}
 | 
						|
 | 
						|
	// Non-blocking query method: doesn't read the response
 | 
						|
	function queryIgnoreResult() 
 | 
						|
	{
 | 
						|
		$args = func_get_args();
 | 
						|
		$method = array_shift($args);
 | 
						|
 | 
						|
		if (!$this->socket || $this->protocol == 0) 
 | 
						|
		{
 | 
						|
			throw new Exception('transport error - Client not initialized', -32300);
 | 
						|
		}
 | 
						|
 | 
						|
		$request = new Request($method, $args);
 | 
						|
 | 
						|
		// Check if the request is greater than 1024 Kbytes to avoid errors
 | 
						|
		// If the method is system.multicall, make two calls (possibly recursively)
 | 
						|
		if ($request->getLength() > 1024*1024-8)
 | 
						|
		{
 | 
						|
			if ($method == 'system.multicall' && isset($args[0])) 
 | 
						|
			{
 | 
						|
				$count = count($args[0]);
 | 
						|
				// If count is 1, query cannot be reduced
 | 
						|
				if ($count < 2) 
 | 
						|
				{
 | 
						|
					throw new Exception('transport error - request too large!', -32700);
 | 
						|
				}
 | 
						|
				$length = floor($count/2);
 | 
						|
 | 
						|
				$args1 = array_slice($args[0], 0, $length);
 | 
						|
				$args2 = array_slice($args[0], $length, ($count-$length));
 | 
						|
 | 
						|
				$res1 = $this->queryIgnoreResult('system.multicall', $args1);
 | 
						|
				$res2 = $this->queryIgnoreResult('system.multicall', $args2);
 | 
						|
				return ($res1 && $res2);
 | 
						|
			}
 | 
						|
			// If the method is not a multicall, just stop
 | 
						|
			else 
 | 
						|
			{
 | 
						|
				throw new Exception('transport error - request too large!', -32700);
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		$this->sendRequest($request);
 | 
						|
	}
 | 
						|
	
 | 
						|
	function getResponse() 
 | 
						|
	{
 | 
						|
		// methodResponses can only have one param - return that
 | 
						|
		return $this->message->params[0];
 | 
						|
	}
 | 
						|
	
 | 
						|
	function readCallbacks($timeout = 2000) 
 | 
						|
	{
 | 
						|
		if (!$this->socket || $this->protocol == 0) 
 | 
						|
			throw new Exception('transport error - Client not initialized', -32300);
 | 
						|
		if ($this->protocol == 1)
 | 
						|
			return false;
 | 
						|
 | 
						|
		// flo: moved to end
 | 
						|
		//$something_received = count($this->cb_message)>0;
 | 
						|
		$contents = '';
 | 
						|
		$contents_length = 0;
 | 
						|
 | 
						|
		@stream_set_timeout($this->socket, 0, 10000);  // timeout 10 ms (to read available data)
 | 
						|
		// (assignment in arguments is forbidden since php 5.1.1)
 | 
						|
		$read = array($this->socket);
 | 
						|
		$write = NULL;
 | 
						|
		$except = NULL;
 | 
						|
		$nb = false;
 | 
						|
		
 | 
						|
		try
 | 
						|
		{
 | 
						|
			$nb = @stream_select($read, $write, $except, 0, $timeout);
 | 
						|
		}
 | 
						|
		catch (\Exception $e)
 | 
						|
		{
 | 
						|
			if (strpos($e->getMessage(), 'Invalid CRT') !== false)
 | 
						|
			{
 | 
						|
				$nb = true;
 | 
						|
			}
 | 
						|
			elseif (strpos($e->getMessage(), 'Interrupted system call') !== false)
 | 
						|
			{
 | 
						|
				return;
 | 
						|
			}
 | 
						|
			else
 | 
						|
			{
 | 
						|
				throw $e;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		
 | 
						|
		// workaround for stream_select bug with amd64
 | 
						|
		if ($nb !== false)
 | 
						|
		{
 | 
						|
			$nb = count($read);
 | 
						|
		}
 | 
						|
 | 
						|
		while ($nb !== false && $nb > 0) 
 | 
						|
		{
 | 
						|
			$timeout = 0;  // we don't want to wait for the full time again, just flush the available data
 | 
						|
 | 
						|
			$size = 0;
 | 
						|
			$recvhandle = 0;
 | 
						|
			// Get result
 | 
						|
			$contents = fread($this->socket, 8);
 | 
						|
			if (strlen($contents) == 0) 
 | 
						|
			{
 | 
						|
				throw new Exception('transport error - connection interrupted!', -32700);
 | 
						|
			}
 | 
						|
			$array_result = unpack('Vsize/Vhandle', $contents);
 | 
						|
			$size = $array_result['size'];
 | 
						|
			$recvhandle = $array_result['handle'];
 | 
						|
 | 
						|
			if ($recvhandle == 0 || $size == 0) 
 | 
						|
			{
 | 
						|
				throw new Exception('transport error - connection interrupted!', -32700);
 | 
						|
			}
 | 
						|
			if ($size > SIZE_MAX) 
 | 
						|
			{
 | 
						|
				throw new Exception("transport error - answer too big ($size)", -32700);
 | 
						|
			}
 | 
						|
			
 | 
						|
			self::$received += $size;
 | 
						|
 | 
						|
			$contents = '';
 | 
						|
			$contents_length = 0;
 | 
						|
			while ($contents_length < $size) 
 | 
						|
			{
 | 
						|
				$contents .= fread($this->socket, $size-$contents_length);
 | 
						|
				$contents_length = strlen($contents);
 | 
						|
			}
 | 
						|
 | 
						|
			if (($recvhandle & 0x80000000) == 0) 
 | 
						|
			{
 | 
						|
				// this is a callback. handle= $recvhandle, xml-rpc= $contents
 | 
						|
				//echo 'CALLBACK('.$contents_length.')[ '.$contents.' ]' . LF;
 | 
						|
				$new_cb_message = new Message($contents);
 | 
						|
				if ($new_cb_message->parse() && $new_cb_message->messageType != 'fault') 
 | 
						|
				{
 | 
						|
					array_push($this->cb_message, array($new_cb_message->methodName, $new_cb_message->params));
 | 
						|
				}
 | 
						|
				// flo: moved to end ...
 | 
						|
				// $something_received = true;
 | 
						|
			}
 | 
						|
 | 
						|
			// (assignment in arguments is forbidden since php 5.1.1)
 | 
						|
			$read = array($this->socket);
 | 
						|
			$write = NULL;
 | 
						|
			$except = NULL;
 | 
						|
			
 | 
						|
			try
 | 
						|
			{
 | 
						|
				$nb = @stream_select($read, $write, $except, 0, $timeout);
 | 
						|
			}
 | 
						|
			catch (\Exception $e)
 | 
						|
			{
 | 
						|
				if (strpos($e->getMessage(), 'Invalid CRT') !== false)
 | 
						|
				{
 | 
						|
					$nb = true;
 | 
						|
				}
 | 
						|
				else
 | 
						|
				{
 | 
						|
					throw $e;
 | 
						|
				}
 | 
						|
			}
 | 
						|
			
 | 
						|
			// workaround for stream_select bug with amd64
 | 
						|
			if ($nb !== false)
 | 
						|
			{
 | 
						|
				$nb = count($read);
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return !empty($this->cb_message);
 | 
						|
	}
 | 
						|
 | 
						|
	function getCallbackResponses() 
 | 
						|
	{
 | 
						|
		// (look at the end of basic.php for an example)
 | 
						|
		$messages = $this->cb_message;
 | 
						|
		$this->cb_message = array();
 | 
						|
		return $messages;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
?>
 |