<?php
/**********************************************************\
|                                                          |
| The implementation of PHPRPC Protocol 3.0                |
|                                                          |
| phprpc_server.php                                        |
|                                                          |
| Release 3.0.1                                            |
| Copyright by Team-PHPRPC                                 |
|                                                          |
| WebSite:  http://www.phprpc.org/                         |
|           http://www.phprpc.net/                         |
|           http://www.phprpc.com/                         |
|           http://sourceforge.net/projects/php-rpc/       |
|                                                          |
| Authors:  Ma Bingyao <andot@ujn.edu.cn>                  |
|                                                          |
| This file may be distributed and/or modified under the   |
| terms of the GNU General Public License (GPL) version    |
| 2.0 as published by the Free Software Foundation and     |
| appearing in the included file LICENSE.                  |
|                                                          |
\**********************************************************/

/* PHPRPC Server for PHP.
 *
 * Copyright: Ma Bingyao <andot@ujn.edu.cn>
 * Version: 3.0
 * LastModified: Apr 12, 2010
 * This library is free.  You can redistribute it and/or modify it under GPL.
 *
/*
 * Interfaces
 *
 * function add($a, $b) {
 *     return $a + $b;
 * }
 * function sub($a, $b) {
 *     return $a - $b;
 * }
 * function inc(&$n) {
 *     return $n++;
 * }
 * include('phprpc_server.php');
 * $server = new PHPRPC_Server();
 * $server->add(array('add', 'sub'));
 * $server->add('inc');
 * $server->setCharset('UTF-8');
 * $server->setDebugMode(true);
 * $server->start();
 *
 */

class PHPRPC_Server
{
    var $callback;
    var $charset;
    var $encode;
    var $ref;
    var $encrypt;
    var $enableGZIP;
    var $debug;
    var $keylen;
    var $key;
    var $errno;
    var $errstr;
    var $functions;
    var $cid;
    var $buffer;
    // Private Methods
    function addJsSlashes($str, $flag)
    {
        if ($flag)
        {
            $str = addcslashes($str, "\0..\006\010..\012\014..\037\042\047\134\177..\377");
        }
        else
        {
            $str = addcslashes($str, "\0..\006\010..\012\014..\037\042\047\134\177");
        }
        return str_replace(array(chr(7), chr(11)), array('\007', '\013'), $str);
    }
    function encodeString($str, $flag = true)
    {
        if ($this->encode)
        {
            return base64_encode($str);
        }
        else
        {
            return $this->addJsSlashes($str, $flag);
        }
    }
    function encryptString($str, $level)
    {
        if ($this->encrypt >= $level)
        {
            $str = xxtea_encrypt($str, $this->key);
        }
        return $str;
    }
    function decryptString($str, $level)
    {
        if ($this->encrypt >= $level) {
            $str = xxtea_decrypt($str, $this->key);
        }
        return $str;
    }
    function sendHeader()
    {
        header("HTTP/1.1 200 OK");
        header("Content-Type: text/plain; charset={$this->charset}");
        header("X-Powered-By: PHPRPC Server/3.0");
        header('P3P: CP="CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE GOV"');
        header('Expires: ' . gmdate('D, d M Y H:i:s') . ' GMT');
        header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
    }
    function getRequestURL()
    {
        if (!isset($_SERVER['HTTPS']) ||
            $_SERVER['HTTPS'] == 'off'  ||
            $_SERVER['HTTPS'] == '') {
            $scheme = 'http';
        }
        else
        {
            $scheme = 'https';
        }
        $host = $_SERVER['SERVER_NAME'];
        $port = $_SERVER['SERVER_PORT'];
        $path = $_SERVER['SCRIPT_NAME'];

        return $scheme . '://' . $host . (($port == 80) ? '' : ':' . $port) . $path;
    }
    function sendURL()
    {
        if (SID != "")
        {
            $url = $this->getRequestURL();
            if (count($_GET) > 0) {
                $url .= '?' . strip_tags(SID);
                foreach ($_GET as $key => $value)
                {
                    if (strpos(strtolower($key), 'phprpc_') !== 0)
                    {
                        $url .= '&' . $key . '=' . urlencode($value);
                    }
                }
            }
            $this->buffer .= "phprpc_url=\"" . $this->encodeString($url) . "\";\r\n";
        }
    }
    function gzip($buffer)
    {
        $len = strlen($buffer);
        if ($this->enableGZIP && strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip,deflate'))
        {
            $gzbuffer = gzencode($buffer);
            $gzlen = strlen($gzbuffer);
            if ($len > $gzlen) {
                header("Content-Length: $gzlen");
                header("Content-Encoding: gzip");
                return $gzbuffer;
            }
        }
        header("Content-Length: $len");

        return $buffer;
    }
    function sendCallback()
    {
        $this->buffer .= $this->callback;
        echo $this->gzip($this->buffer);
        ob_end_flush();
        restore_error_handler();
        if (function_exists('restore_exception_handler'))
        {
            restore_exception_handler();
        }
        exit();
    }
    function sendFunctions()
    {
        $this->buffer .= "phprpc_functions=\"" . $this->encodeString(PHPRPC::serialize_fix(array_keys($this->functions))) . "\";\r\n";
        $this->sendCallback();
    }
    function sendOutput($output)
    {
        if ($this->encrypt >= 3)
        {
            $this->buffer .= "phprpc_output=\"" . $this->encodeString(xxtea_encrypt($output, $this->key)) . "\";\r\n";
        }
        else
        {
            $this->buffer .= "phprpc_output=\"" . $this->encodeString($output, false) . "\";\r\n";
        }
    }
    function sendError($output = NULL)
    {
        if (is_null($output)) {
            $output = ob_get_clean();
        }
        $this->buffer .= "phprpc_errno=\"{$this->errno}\";\r\n";
        $this->buffer .= "phprpc_errstr=\"" . $this->encodeString($this->errstr, false) . "\";\r\n";
        $this->sendOutput($output);
        $this->sendCallback();
    }
    function fatalErrorHandler($buffer)
    {
        if (preg_match('/<b>(.*?) error<\/b>:(.*?)<br/', $buffer, $match)) {
            if ($match[1] == 'Fatal') {
                $errno = E_ERROR;
            }
            else {
                $errno = E_COMPILE_ERROR;
            }
            if ($this->debug) {
                $errstr = preg_replace('/<.*?>/', '', $match[2]);
            }
            else {
                $errstr = preg_replace('/ in <b>.*<\/b>$/', '', $match[2]);
            }

            $buffer = "phprpc_errno=\"{$errno}\";\r\n" .
                      "phprpc_errstr=\"" . $this->encodeString(trim($errstr), false) . "\";\r\n" .
                      "phprpc_output=\"\";\r\n" .
                       $this->callback;
            $buffer = $this->gzip($buffer);
        }
        return $buffer;
    }
    function errorHandler($errno, $errstr, $errfile, $errline)
    {
        if ($this->debug) {
            $errstr .= " in $errfile on line $errline";
        }
        if (($errno == E_ERROR) or ($errno == E_CORE_ERROR) or
            ($errno == E_COMPILE_ERROR) or ($errno == E_USER_ERROR)) {
            $this->errno = $errno;
            $this->errstr = $errstr;
            $this->sendError();
        }
        else {
            if (($errno == E_NOTICE) or ($errno == E_USER_NOTICE)) {
                if ($this->errno == 0) {
                    $this->errno = $errno;
                    $this->errstr = $errstr;
                }
            }
            else {
                if (($this->errno == 0) or
                    ($this->errno == E_NOTICE) or
                    ($this->errno == E_USER_NOTICE)) {
                    $this->errno = $errno;
                    $this->errstr = $errstr;
                }
            }
        }
        return true;
    }
    function exceptionHandler($exception)
    {
        $this->errno = $exception->getCode();
        $this->errstr = $exception->getMessage();
        if ($this->debug)
        {
            $this->errstr .= "\nfile: " . $exception->getFile() .
                             "\nline: " . $exception->getLine() .
                             "\ntrace: " . $exception->getTraceAsString();
        }
        $this->sendError();
    }
    function initErrorHandler()
    {
        $this->errno = 0;
        $this->errstr = "";
        set_error_handler(array(&$this, 'errorHandler'));
        if (function_exists('set_exception_handler'))
        {
            set_exception_handler(array(&$this, 'exceptionHandler'));
        }
    }
    function call($function, &$args)
    {
        if ($this->ref)
        {
            $arguments = array();
            for ($i = 0; $i < count($args); $i++)
            {
                $arguments[$i] = &$args[$i];
            }
        }
        else
        {
            $arguments = $args;
        }
        return call_user_func_array($function, $arguments);
    }
    function getRequest($name) {
        $result = $_REQUEST[$name];
        if (get_magic_quotes_gpc()) {
            $result = stripslashes($result);
        }
        return $result;
    }
    function getBooleanRequest($name) {
        $var = true;
        if (isset($_REQUEST[$name])) {
            $var = strtolower($this->getRequest($name));
            if ($var == "false") {
                $var = false;
            }
        }
        return $var;
    }
    function initEncode() {
        $this->encode = $this->getBooleanRequest('phprpc_encode');
    }
    function initRef() {
        $this->ref = $this->getBooleanRequest('phprpc_ref');
    }
    function initCallback() {
        if (isset($_REQUEST['phprpc_callback'])) {
            $this->callback = base64_decode($this->getRequest('phprpc_callback'));
        }
        else {
            $this->callback = "";
        }
    }
    function initKeylen() {
        if (isset($_REQUEST['phprpc_keylen'])) {
            $this->keylen = (int)$this->getRequest('phprpc_keylen');
        }
        else if (isset($_SESSION[$this->cid])) {
            $session = unserialize(base64_decode($_SESSION[$this->cid]));
            if (isset($session['keylen'])) {
                $this->keylen = $session['keylen'];                
            }
            else {
                $this->keylen = 128;
            }
        }
        else {
            $this->keylen = 128;
        }
    }
    function initClientID() {
        $this->cid = 0;
        if (isset($_REQUEST['phprpc_id'])) {
            $this->cid = $this->getRequest('phprpc_id');
        }
        $this->cid = "phprpc_" . $this->cid;
    }
    function initEncrypt() {
        $this->encrypt = false;
        if (isset($_REQUEST['phprpc_encrypt'])) {
            $this->encrypt = $this->getRequest('phprpc_encrypt');
            if ($this->encrypt === "true") $this->encrypt = true;
            if ($this->encrypt === "false") $this->encrypt = false;
        }
    }
    function initKey() {
        if ($this->encrypt == 0) {
            return;
        }
        else if (isset($_SESSION[$this->cid]))
        {
            $session = unserialize(base64_decode($_SESSION[$this->cid]));
            if (isset($session['key'])) {
                $this->key = $session['key'];
                //require_once('xxtea.php');
                PHPRPC::xxtea();
                return;
            }
        }
        $this->errno   = E_ERROR;
        $this->errstr  = "Can't find the key for decryption.";
        $this->encrypt = 0;
        $this->sendError();
    }
    function getArguments() {
        if (isset($_REQUEST['phprpc_args'])) {
            $arguments = unserialize($this->decryptString(base64_decode($this->getRequest('phprpc_args')), 1));
            ksort($arguments);
        }
        else {
            $arguments = array();
        }
        return $arguments;
    }
    function callFunction() {
        $this->initKey();
        $function = strtolower($this->getRequest('phprpc_func'));
        if (array_key_exists($function, $this->functions))
        {
            $function = $this->functions[$function];
            $arguments = $this->getArguments();
            $result = $this->encodeString($this->encryptString(PHPRPC::serialize_fix($this->call($function, $arguments)), 2));
            $output = ob_get_clean();
            $this->buffer .= "phprpc_result=\"$result\";\r\n";
            if ($this->ref)
            {
                $arguments = $this->encodeString($this->encryptString(PHPRPC::serialize_fix($arguments), 1));
                $this->buffer .= "phprpc_args=\"$arguments\";\r\n";
            }
        }
        else {
            $this->errno = E_ERROR;
            $this->errstr = "Can't find this function $function().";
            $output = ob_get_clean();
        }
        $this->sendError($output);
    }
    function keyExchange()
    {
        $this->initKeylen();
        if (isset($_SESSION[$this->cid]))
        {
            $session = unserialize(base64_decode($_SESSION[$this->cid]));
        }
        else
        {
            $session = array();
        }        
        if ($this->encrypt === true)
        {
//            require_once(dirname(__FILE__) .'/dhparams.php');
            $DHParams = PHPRPC::DHParams($this->keylen);
            $this->keylen = $DHParams->getL();
            $encrypt = $DHParams->getDHParams();
            $x = BigInt::random($this->keylen - 1, true);
            $session['x'] = BigInt::num2dec($x);
            $session['p'] = $encrypt['p'];
            $session['keylen'] = $this->keylen;
            $encrypt['y'] = BigInt::num2dec(BigInt::powmod(BigInt::dec2num($encrypt['g']), $x, BigInt::dec2num($encrypt['p'])));
            $this->buffer .= "phprpc_encrypt=\"" . $this->encodeString(PHPRPC::serialize_fix($encrypt)) . "\";\r\n";
            if ($this->keylen != 128) {
                $this->buffer .= "phprpc_keylen=\"{$this->keylen}\";\r\n";
            }
            $this->sendURL();
        }
        else {
            $y   = BigInt::dec2num($this->encrypt);
            $x   = BigInt::dec2num($session['x']);
            $p   = BigInt::dec2num($session['p']);
            $key = BigInt::powmod($y, $x, $p);
            if ($this->keylen == 128) {
                $key = BigInt::num2str($key);
            }
            else {
                $key = pack('H*', md5(BigInt::num2dec($key)));
            }
            $session['key'] = str_pad($key, 16, "\0", STR_PAD_LEFT);
        }
        $_SESSION[$this->cid] = base64_encode(serialize($session));
        $this->sendCallback();
    }
    function initSession()
    {
        @ob_start();
        ob_implicit_flush(0);
        session_start();
    }
    function initOutputBuffer() {
        @ob_start(array(&$this, "fatalErrorHandler"));
        ob_implicit_flush(0);
        $this->buffer = "";
    }

    // Public Methods
    function __construct()
    {
//        require_once('compat.php');
        PHPRPC::init();
        $this->functions = array();
        $this->charset = 'UTF-8';
        $this->debug = false;
        $this->enableGZIP = false;
    }

    function add($functions, $obj = NULL, $aliases = NULL)
    {
        if (is_null($functions) || (gettype($functions) != gettype($aliases) && !is_null($aliases))) {
            return false;
        }
        if (is_object($functions)) {
            $obj = $functions;
            $functions = get_class_methods(get_class($obj));
            $aliases = $functions;
        }
        if (is_null($aliases)) {
            $aliases = $functions;
        }
        if (is_string($functions)) {
            if (is_null($obj)) {
                $this->functions[strtolower($aliases)] = $functions;
            }
            else if (is_object($obj)) {
                $this->functions[strtolower($aliases)] = array(&$obj, $functions);
            }
            else if (is_string($obj)) {
                $this->functions[strtolower($aliases)] = array($obj, $functions);
            }
        }
        else {
            if (count($functions) != count($aliases)) {
                return false;
            }
            foreach ($functions as $key => $function) {
                $this->add($function, $obj, $aliases[$key]);
            }
        }
        return true;
    }

    function setCharset($charset)
    {
        $this->charset = $charset;
    }

    function setDebugMode($debug)
    {
        $this->debug = $debug;
    }

    function setEnableGZIP($enableGZIP)
    {
        $this->enableGZIP = $enableGZIP;
    }

    function start()
    {
        while(ob_get_length() !== false) @ob_end_clean();
        $this->initOutputBuffer();
        $this->sendHeader();
        $this->initErrorHandler();
        $this->initEncode();
        $this->initCallback();
        $this->initRef();
        $this->initClientID();
        $this->initEncrypt();

        if (isset($_REQUEST['phprpc_func']))
        {
            $this->callFunction();
        }
        else if ($this->encrypt != false)
        {
            $this->keyExchange();
        }
        else
        {
            $this->sendFunctions();
        }
    }
}

PHPRPC_Server::initSession();
