modxcms/revolution

View on GitHub
core/model/modx/jsonrpc/jsonrpc.inc

Summary

Maintainability
Test Coverage
<?php
/**
 * @package modx
 * @subpackage jsonrpc
 */

/**
 * JSON extension to the PHP-XMLRPC lib
 *
 * For more info see:
 * http://www.json.org/
 * http://json-rpc.org/
 *
 * @author Gaetano Giunta
 * @version $Id: jsonrpc.inc,v 1.30 2007/02/22 13:50:18 ggiunta Exp $
 * @copyright (c) 2005-2006 G. Giunta
 *
 * @todo the JSON proposed RFC states that when making json calls, we should
 *       specify an 'accep: application/json' http header. Currently we either
 *       do not otuput an 'accept' header or specify  'any' (in curl mode)
 **/

    // requires: xmlrpc.inc 2.0 or later

    // Note: the json spec omits \v, but it is present in ECMA-262, so we allow it
    $GLOBALS['ecma262_entities'] = array(
        'b' => chr(8),
        'f' => chr(12),
        'n' => chr(10),
        'r' => chr(13),
        't' => chr(9),
        'v' => chr(11)
    );

    // tables used for transcoding different charsets into us-ascii javascript

    $GLOBALS['ecma262_iso88591_Entities']=array();
    $GLOBALS['ecma262_iso88591_Entities']['in'] = array();
    $GLOBALS['ecma262_iso88591_Entities']['out'] = array();
    for ($i = 0; $i < 32; $i++)
    {
        $GLOBALS['ecma262_iso88591_Entities']['in'][] = chr($i);
        $GLOBALS['ecma262_iso88591_Entities']['out'][] = sprintf('\u%\'04x', $i);
    }
    for ($i = 160; $i < 256; $i++)
    {
        $GLOBALS['ecma262_iso88591_Entities']['in'][] = chr($i);
        $GLOBALS['ecma262_iso88591_Entities']['out'][] = sprintf('\u%\'04x', $i);
    }

    /**
    * Encode php strings to valid JSON unicode representation.
    * All chars outside ASCII range are converted to \uXXXX for maximum portability.
    * @param string $data (in iso-8859-1 charset by default)
    * @param string charset of source string, defaults to $GLOBALS['xmlrpc_internalencoding']
    * @param string charset of the encoded string, defaults to ASCII for maximum interoperabilty
    * @return string
    * @access private
    * @todo add support for UTF-16 as destination charset instead of ASCII
    * @todo add support for UTF-16 as source charset
    */
    function json_encode_entities($data, $src_encoding='', $dest_encoding='')
    {
        if ($src_encoding == '')
        {
            // lame, but we know no better...
            $src_encoding = $GLOBALS['xmlrpc_internalencoding'];
        }

        switch(strtoupper($src_encoding.'_'.$dest_encoding))
        {
            case 'ISO-8859-1_':
            case 'ISO-8859-1_US-ASCII':
                $escaped_data = str_replace(array('\\', '"', '/', "\t", "\n", "\r", chr(8), chr(11), chr(12)), array('\\\\', '\"', '\/', '\t', '\n', '\r', '\b', '\v', '\f'), $data);
                $escaped_data = str_replace($GLOBALS['ecma262_iso88591_Entities']['in'], $GLOBALS['ecma262_iso88591_Entities']['out'], $escaped_data);
                break;
            case 'ISO-8859-1_UTF-8':
                $escaped_data = str_replace(array('\\', '"', '/', "\t", "\n", "\r", chr(8), chr(11), chr(12)), array('\\\\', '\"', '\/', '\t', '\n', '\r', '\b', '\v', '\f'), $data);
                $escaped_data = utf8_encode($escaped_data);
                break;
            case 'ISO-8859-1_ISO-8859-1':
            case 'US-ASCII_US-ASCII':
            case 'US-ASCII_UTF-8':
            case 'US-ASCII_':
            case 'US-ASCII_ISO-8859-1':
            case 'UTF-8_UTF-8':
                $escaped_data = str_replace(array('\\', '"', '/', "\t", "\n", "\r", chr(8), chr(11), chr(12)), array('\\\\', '\"', '\/', '\t', '\n', '\r', '\b', '\v', '\f'), $data);
                break;
            case 'UTF-8_':
            case 'UTF-8_US-ASCII':
            case 'UTF-8_ISO-8859-1':
    // NB: this will choke on invalid UTF-8, going most likely beyond EOF
    $escaped_data = "";
    // be kind to users creating string jsonrpcvals out of different php types
    $data = (string) $data;
    $ns = strlen ($data);
    for ($nn = 0; $nn < $ns; $nn++)
    {
        $ch = $data[$nn];
        $ii = ord($ch);
        //1 7 0bbbbbbb (127)
        if ($ii < 128)
        {
            /// @todo shall we replace this with a (supposedly) faster str_replace?
            switch($ii){
                case 8:
                    $escaped_data .= '\b';
                    break;
                case 9:
                    $escaped_data .= '\t';
                    break;
                case 10:
                    $escaped_data .= '\n';
                    break;
                case 11:
                    $escaped_data .= '\v';
                    break;
                case 12:
                    $escaped_data .= '\f';
                    break;
                case 13:
                    $escaped_data .= '\r';
                    break;
                case 34:
                    $escaped_data .= '\"';
                    break;
                case 47:
                    $escaped_data .= '\/';
                    break;
                case 92:
                    $escaped_data .= '\\';
                    break;
                default:
                    $escaped_data .= $ch;
            } // switch
        }
        //2 11 110bbbbb 10bbbbbb (2047)
        else if ($ii>>5 == 6)
        {
            $b1 = ($ii & 31);
            $ii = ord($data[$nn+1]);
            $b2 = ($ii & 63);
            $ii = ($b1 * 64) + $b2;
            $ent = sprintf ('\u%\'04x', $ii);
            $escaped_data .= $ent;
            $nn += 1;
        }
        //3 16 1110bbbb 10bbbbbb 10bbbbbb
        else if ($ii>>4 == 14)
        {
            $b1 = ($ii & 31);
            $ii = ord($data[$nn+1]);
            $b2 = ($ii & 63);
            $ii = ord($data[$nn+2]);
            $b3 = ($ii & 63);
            $ii = ((($b1 * 64) + $b2) * 64) + $b3;
            $ent = sprintf ('\u%\'04x', $ii);
            $escaped_data .= $ent;
            $nn += 2;
        }
        //4 21 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
        else if ($ii>>3 == 30)
        {
            $b1 = ($ii & 31);
            $ii = ord($data[$nn+1]);
            $b2 = ($ii & 63);
            $ii = ord($data[$nn+2]);
            $b3 = ($ii & 63);
            $ii = ord($data[$nn+3]);
            $b4 = ($ii & 63);
            $ii = ((((($b1 * 64) + $b2) * 64) + $b3) * 64) + $b4;
            $ent = sprintf ('\u%\'04x', $ii);
            $escaped_data .= $ent;
            $nn += 3;
        }
    }
                break;
            default:
                $escaped_data = '';
                error_log("Converting from $src_encoding to $dest_encoding: not supported...");
        } // switch
        return $escaped_data;

    /*
        $length = strlen($data);
        $escapeddata = "";
        for($position = 0; $position < $length; $position++)
        {
            $character = substr($data, $position, 1);
            $code = ord($character);
            switch($code)
            {
                case 8:
                    $character = '\b';
                    break;
                case 9:
                    $character = '\t';
                    break;
                case 10:
                    $character = '\n';
                    break;
                case 12:
                    $character = '\f';
                    break;
                case 13:
                    $character = '\r';
                    break;
                case 34:
                    $character = '\"';
                    break;
                case 47:
                    $character = '\/';
                    break;
                case 92:
                    $character = '\\\\';
                    break;
                default:
                    if($code < 32 || $code > 159)
                    {
                        $character = "\u".str_pad(dechex($code), 4, '0', STR_PAD_LEFT);
                    }
                    break;
            }
            $escapeddata .= $character;
        }
        return $escapeddata;
        */
    }

    /**
    * Parse a JSON string.
    * NB: try to accept any valid string according to ECMA, even though the JSON
    * spec is much more strict.
    * Assumes input is UTF-8...
    * @param string $data a json string
    * @param bool $return_phpvals if true, do not rebuild jsonrpcval objects, but plain php values
    * @param string $src_encoding
    * @param string $dest_encoding
    * @return bool
    * @access private
    * @todo support for other source encodings than UTF-8
    * @todo optimization creep: build elements of arrays/objects asap instead of counting chars many times
    * @todo we should move to xmlrpc_defencoding and xmlrpc_internalencoding as predefined values, but it would make this even slower...
    *       Maybe just move those two parameters outside of here into callers?
    *
    * @bug parsing of "[1]// comment here" works in ie/ff, but not here
    * @bug parsing of "[.1]" works in ie/ff, but not here
    * @bug parsing of "[01]" works in ie/ff, but not here
    * @bug parsing of "{true:1}" works here, but not in ie/ff
    * @bug parsing of "{a b:1}" works here, but not in ie/ff
    */
    function json_parse($data, $return_phpvals=false, $src_encoding='UTF-8', $dest_encoding='ISO-8859-1')
    {
        // optimization creep: this is quite costly. Is there any better way to achieve it?
        // also note that json does not really allow comments...
        $data = preg_replace(array(
            // eliminate single line comments in '// ...' form
            // REMOVED BECAUSE OF BUGS: 1-does not match at end of non-empty line, 2-eats inside strings, too
            //'#^\s*//(.*)$#m',
            // eliminate multi-line comments in '/* ... */' form, at start of string
            '#^\s*/\*(.*)\*/#Us',
            // eliminate multi-line comments in '/* ... */' form, at end of string
            '#/\*(.*)\*/\s*$#Us'
        ), '', $data);

        $data = trim($data); // remove excess whitespace

        if ($data == '')
        {
            $GLOBALS['_xh']['isf_reason'] = 'Invalid data (empty string?)';
            return false;
        }

//echo "Parsing string (".$data.")\n";
        switch($data[0])
        {
            case '"':
            case "'":
                $len = strlen($data);
                // quoted string: check for closing char first
                if ($data[$len-1] == $data[0] && $len > 1)
                {
                    // UTF8-decode (or encode) string
                    // NB: we MUST do this BEFORE looking for \xNN, \uMMMM or other escape sequences
                    if ($src_encoding == 'UTF-8' && ($dest_encoding == 'ISO-8859-1' || $dest_encoding == 'US-ASCII'))
                    {
                        $data = utf8_decode($data);
                        $len = strlen($data);
                    }
                    else
                    {
                        if ($dest_encoding == 'UTF-8' && ($src_encoding == 'ISO-8859-1' || $src_encoding == 'US-ASCII'))
                        {
                            $data = utf8_encode($data);
                            $len = strlen($data);
                        }
                        //else
                        //{
                        //    $GLOBALS['_xh']['value'] = $GLOBALS['_xh']['ac'];
                        //}
                    }

                    $outdata = '';
                    $delim = $data[0];
                    for ($i = 1; $i < $len-1; $i++)
                    {
                        switch($data[$i])
                        {
                            case '\\':
                                if ($i == $len-2)
                                {
                                    break;
                                }
                                switch($data[$i+1])
                                {
                                    case 'b':
                                    case 'f':
                                    case 'n':
                                    case 'r':
                                    case 't':
                                    case 'v':
                                        $outdata .= $GLOBALS['ecma262_entities'][$data[$i+1]];
                                        $i++;
                                        break;
                                    case 'u':
                                        // most likely unicode code point
                                        if ($dest_encoding == 'UTF-8')
                                        {
                                            /// @todo see if this is faster / works in all cases
                                            //$outdata .= utf8_encode(chr(hexdec(substr($data, $i+4, 2))));

                                            // encode the UTF code point into utf-8...
                                            $ii = hexdec(substr($data, $i+2, 4));
                                            if ($ii < 0x80)
                                            {
                                                $outdata .= chr($ii);
                                            }
                                            else if ($ii <= 0x800)
                                            {
                                                $outdata .= chr(0xc0 | $ii >> 6) . chr(0x80 | ($ii & 0x3f));
                                            }
                                            else if ($ii <= 0x10000)
                                            {
                                                $outdata .= chr(0xe0 | $ii >> 12) . chr(0x80 | ($ii >> 6 & 0x3f)) . chr(0x80 | ($ii & 0x3f));
                                            }
                                            else
                                            {
                                                $outdata .= chr(0xf0 | $ii >> 20) . chr(0x80 | ($ii >> 12 & 0x3f)) . chr(0x80 | ($ii >> 6 & 0x3f)) . chr(0x80 | ($ii & 0x3f));
                                            }
                                            $i += 5;
                                        }
                                        else
                                        {
                                            // Note: we only decode code points below 256, so we take the last 2 chars of the unicode representation
                                            $outdata .= chr(hexdec(substr($data, $i+4, 2)));
                                            $i += 5;
                                        }
                                        break;
                                    case 'x':
                                        // most likely unicode code point in hexadecimal
                                        // Note: the json spec omits this case, but ECMA-262 does not...
                                        if ($dest_encoding == 'UTF-8')
                                        {
                                            // encode the UTF code point into utf-8...
                                            $ii = hexdec(substr($data, $i+2, 2));
                                            if ($ii < 0x80)
                                            {
                                                $outdata .= chr($ii);
                                            }
                                            else if ($ii <= 0x800)
                                            {
                                                $outdata .= chr(0xc0 | $ii >> 6) . chr(0x80 | ($ii & 0x3f));
                                            }
                                            else if ($ii <= 0x10000)
                                            {
                                                $outdata .= chr(0xe0 | $ii >> 12) . chr(0x80 | ($ii >> 6 & 0x3f)) . chr(0x80 | ($ii & 0x3f));
                                            }
                                            else
                                            {
                                                $outdata .= chr(0xf0 | $ii >> 20) . chr(0x80 | ($ii >> 12 & 0x3f)) . chr(0x80 | ($ii >> 6 & 0x3f)) . chr(0x80 | ($ii & 0x3f));
                                            }
                                            $i += 3;
                                        }
                                        else
                                        {
                                            $outdata .= chr(hexdec(substr($data, $i+2, 2)));
                                            $i += 3;
                                        }
                                        break;
                                    case '0':
                                    case '1':
                                    case '2':
                                    case '3':
                                    case '4':
                                    case '5':
                                    case '6':
                                    case '7':
                                    case '8':
                                    case '9':
                                        // Note: ECMA-262 forbids these escapes, we just skip it...
                                        break;
                                    default:
                                        // Note: Javascript 1.5 on http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Guide
                                        // mentions syntax /XXX with X octal number, but ECMA262
                                        // explicitly forbids it...
                                        $outdata .= $data[$i+1];
                                        $i++;
                                } // end of switch on slash char found
                                break;
                            case $delim:
                                // found unquoted end of string in middle of string
                                $GLOBALS['_xh']['isf_reason'] = 'Invalid data (unescaped quote char inside string?)';
                                return false;
                            case "\n":
                            case "\r":
                                $GLOBALS['_xh']['isf_reason'] = 'Invalid data (line terminator char inside string?)';
                                return false;
                            default:
                                $outdata .= $data[$i];
                        }
                    } // end of loop on string chars
//echo "Found a string\n";
                    $GLOBALS['_xh']['vt'] = 'string';
                    $GLOBALS['_xh']['value'] = $outdata;
                }
                else
                {
                    // string without a terminating quote
                    $GLOBALS['_xh']['isf_reason'] = 'Invalid data (string missing closing quote?)';
                    return false;
                }
                break;
            case '[':
            case '{':
                $len = strlen($data);
                // object and array notation: use the same parsing code
                if ($data[0] == '[')
                {
                    if ($data[$len-1] != ']')
                    {
                        // invalid array
                        $GLOBALS['_xh']['isf_reason'] = 'Invalid data (array missing closing bracket?)';
                        return false;
                    }
                    $GLOBALS['_xh']['vt'] = 'array';
                }
                else
                {
                    if ($data[$len-1] != '}')
                    {
                        // invalid object
                        $GLOBALS['_xh']['isf_reason'] = 'Invalid data (object missing closing bracket?)';
                        return false;
                    }
                    $GLOBALS['_xh']['vt'] = 'struct';
                }

                $data = trim(substr($data, 1, -1));
//echo "Parsing array/obj (".$data.")\n";
                if ($data == '')
                {
                    // empty array/object
                    $GLOBALS['_xh']['value'] = array();
                }
                else
                {
                    $valuestack = array();
                    $last = array('type' => 'sl', 'start' => 0);
                    $len = strlen($data);
                    $value = array();
                    $keypos = null;
                    //$ac = '';
                    $vt = '';
                    //$start = 0;
                    for ($i = 0; $i <= $len; $i++)
                    {
                        if ($i == $len || ($data[$i] == ',' && $last['type'] == 'sl'))
                        {

                            // end of element: push it onto array
                            $slice = substr($data, $last['start'], ($i - $last['start']));
                            //$slice = trim($slice); useless here, sincewe trim it on sub-elementparsing
//echo "Found slice (".$slice.")\n";

                            //$valuestack[] = $last; // necessario ???
                            //$last = array('type' => 'sl', 'start' => ($i + 1));
                            if ($GLOBALS['_xh']['vt'] == 'array')
                            {
                                if ($slice == '')
                                {
                                    // 'elided' element: ecma supports it, so do we
                                    // what should happen here in fact is that
                                    // "array index is augmented and element is undefined"

                                    // NOTE: Firefox's js engine does not create
                                    // trailing undefined elements, while IE does...
                                    //if ($i < $len)
                                    //{
                                        if ($return_phpvals)
                                        {
                                            $value[] = null;
                                        }
                                        else
                                        {
                                            $value[] = new jsonrpcval(null, 'null');
                                        }
                                    //}
                                }
                                else
                                {
                                    if (!json_parse($slice, $return_phpvals, $src_encoding, $dest_encoding))
                                    {
                                        return false;
                                    }
                                    else
                                    {
                                        $value[] = $GLOBALS['_xh']['value'];
                                        $GLOBALS['_xh']['vt'] = 'array';
                                    }
                                }
                            }
                            else
                            {
                                if (!$keypos)
                                {
                                    $GLOBALS['_xh']['isf_reason'] = 'Invalid data (missing object member name?)';
                                    return false;
                                }
                                else
                                {
                                    if (!json_parse(substr($data, $last['start'], $keypos-$last['start']), true, $src_encoding, $dest_encoding) ||
                                        $GLOBALS['_xh']['vt'] != 'string')
                                    {
                                        // object member name received unquoted: what to do???
                                        // be tolerant as much as we can. ecma tolerates numbers as identifiers, too...
                                        $key = trim(substr($data, $last['start'], $keypos-$last['start']));
                                    }
                                    else
                                    {
                                        $key = $GLOBALS['_xh']['value'];
                                    }

//echo "Use extension: $use_extension\n";
                                    if (!json_parse(substr($data, $keypos+1, $i-$keypos-1), $return_phpvals, $src_encoding, $dest_encoding))
                                    {
                                        return false;
                                    }
                                    $value[$key] = $GLOBALS['_xh']['value'];
                                    $GLOBALS['_xh']['vt'] = 'struct';
                                    $keypos = null;
                                }
                            }
                            $last['start'] = $i + 1;
                            $vt = ''; // reset type of val found
                        }
                        else if ($data[$i] == '"' || $data[$i] == "'")
                        {
                            // found beginning of string: run till end
                            $ok = false;
                            for ($j = $i+1; $j < $len; $j++)
                            {
                                if ($data[$j] == $data[$i])
                                {
                                    $ok = true;
                                    break;
                                }
                                else if($data[$j] == '\\')
                                {
                                    $j++;
                                }
                            }
                            if ($ok)
                            {
                                $i = $j; // advance pointer to end of string
                                $vt = 'st';
                            }
                            else
                            {
                                $GLOBALS['_xh']['isf_reason'] = 'Invalid data (string missing closing quote?)';
                                return false;
                            }
                        }
                        else if ($data[$i] == "[")
                        {
                            $valuestack[] = $last;
                            $last = array('type' => 'ar', 'start' => $i);
                        }
                        else if ($data[$i] == '{')
                        {
                            $valuestack[] = $last;
                            $last = array('type' => 'ob', 'start' => $i);
                        }
                        else if ($data[$i] == "]")
                        {
                            if ($last['type'] == 'ar')
                            {
                                $last = array_pop($valuestack);
                                $vt = 'ar';
                            }
                            else
                            {
                                $GLOBALS['_xh']['isf_reason'] = 'Invalid data (unmatched array closing bracket?)';
                                return false;
                            }
                        }
                        else if ($data[$i] == '}')
                        {
                            if ($last['type'] == 'ob')
                            {
                                $last = array_pop($valuestack);
                                $vt = 'ob';
                            }
                            else
                            {
                                $GLOBALS['_xh']['isf_reason'] = 'Invalid data (unmatched object closing bracket?)';
                                return false;
                            }
                        }
                        else if ($data[$i] == ':' && $last['type'] == 'sl' && !$keypos)
                        {
//echo "Found key stop at pos. $i\n";
                            $keypos = $i;
                        }
                        else if ($data[$i] == '/' && $i < $len-1 && $data[$i+1] == "*")
                        {
                            // found beginning of comment: run till end
                            $ok = false;
                            for ($j = $i+2; $j < $len-1; $j++)
                            {
                                if ($data[$j] == '*' && $data[$j+1] == '/')
                                {
                                    $ok = true;
                                    break;
                                }
                            }
                            if ($ok)
                            {
                                $i = $j+1; // advance pointer to end of string
                            }
                            else
                            {
                                $GLOBALS['_xh']['isf_reason'] = 'Invalid data (comment missing closing tag?)';
                                return false;
                            }
                        }

                    }
                    $GLOBALS['_xh']['value'] = $value;
                }
                //return true;
                break;
            default:
//echo "Found a scalar val (not string): '$data'\n";
                // be tolerant of uppercase chars in numbers/booleans/null
                $data = strtolower($data);
                if ($data == "true")
                {
//echo "Found a true\n";
                    $GLOBALS['_xh']['value'] = true;
                    $GLOBALS['_xh']['vt'] = 'boolean';
                }
                else if ($data == "false")
                {
//echo "Found a false\n";
                    $GLOBALS['_xh']['value'] = false;
                    $GLOBALS['_xh']['vt'] = 'boolean';
                }
                else if ($data == "null")
                {
//echo "Found a null\n";
                    $GLOBALS['_xh']['value'] = null;
                    $GLOBALS['_xh']['vt'] = 'null';
                }
                // we could use is_numeric here, but rules are slightly different,
                // e.g. 012 is NOT valid according to JSON or ECMA, but browsers inetrpret it as octal
                /// @todo add support for .5
                /// @todo add support for numbers in octal notation, eg. 010
                else if (preg_match("#^-?(0|[1-9][0-9]*)(\.[0-9]*)?([e][+-]?[0-9]+)?$#" ,$data))
                {
                    if (preg_match('#[.e]#', $data))
                    {
//echo "Found a double\n";
                        // floating point
                        $GLOBALS['_xh']['value'] = (double)$data;
                        $GLOBALS['_xh']['vt'] = 'double';
                    }
                    else
                    {
//echo "Found an int\n";
                        //integer
                        $GLOBALS['_xh']['value'] = (int)$data;
                        $GLOBALS['_xh']['vt'] = 'int';
                    }
                    //return true;
                }
                else if (preg_match("#^0x[0-9a-f]+$#", $data))
                {
                    // int in hex notation: not in JSON, but in ECMA...
                    $GLOBALS['_xh']['vt'] = 'int';
                    $GLOBALS['_xh']['value'] = hexdec(substr($data, 2));
                }
                else
                {
                    $GLOBALS['_xh']['isf_reason'] = 'Invalid data';
                    return false;
                }
        } // switch $data[0]

        if (!$return_phpvals)
        {
            $GLOBALS['_xh']['value'] = new jsonrpcval($GLOBALS['_xh']['value'], $GLOBALS['_xh']['vt']);
        }

        return true;

    }

    /**
    * Used in place of json_parse to take advantage of native json decoding when available:
    * it parses either a jsonrpc request or a response.
    * NB: php native decoding of json balks anyway at anything but array / struct as top level element
    * @access private
    * @bug unicode chars are handled differently from this and json_parse...
    * @todo add support for src and dest encoding!!!
    */
    function json_parse_native($data)
    {
//echo "Parsing string - internal way (".$data.")\n";
        $out = json_decode($data, true);
        if (!is_array($out))
        {
            //$GLOBALS['_xh']['isf'] = 2;
            $GLOBALS['_xh']['isf_reason'] = 'JSON parsing failed';
            return false;
        }
        // decoding will be fine for a jsonrpc error response, so we have to
        // check for it by hand here...
        //else if (array_key_exists('error', $out) && $out['error'] != null)
        //{
        //    $GLOBALS['_xh']['isf'] = 1;
            //$GLOBALS['_xh']['value'] = $out['error'];
        //}
        else
        {
            $GLOBALS['_xh']['value'] = $out;
            return true;
        }
    }

    /**
    * Parse a json string, expected to be jsonrpc request format
    * @access private
    */
    function jsonrpc_parse_req($data, $return_phpvals=false, $use_extension=false, $src_encoding='')
    {
        $GLOBALS['_xh']['isf']=0;
        $GLOBALS['_xh']['isf_reason']='';
        if ($return_phpvals && $use_extension)
        {
            $ok = json_parse_native($data);
        }
        else
        {
            $ok = json_parse($data, $return_phpvals, $src_encoding);
        }
        if ($ok)
        {
            if (!$return_phpvals)
                $GLOBALS['_xh']['value'] = @$GLOBALS['_xh']['value']->me['struct'];

            if (!is_array($GLOBALS['_xh']['value']) || !array_key_exists('method', $GLOBALS['_xh']['value'])
                || !array_key_exists('params', $GLOBALS['_xh']['value']) || !array_key_exists('id', $GLOBALS['_xh']['value']))
            {
                $GLOBALS['_xh']['isf_reason'] = 'JSON parsing did not return correct jsonrpc request object';
                return false;
            }
            else
            {
                $GLOBALS['_xh']['method'] = $GLOBALS['_xh']['value']['method'];
                $GLOBALS['_xh']['params'] = $GLOBALS['_xh']['value']['params'];
                $GLOBALS['_xh']['id'] = $GLOBALS['_xh']['value']['id'];
                if (!$return_phpvals)
                {
                    /// @todo we should check for appropriate type for method name and params array...
                    $GLOBALS['_xh']['method'] = $GLOBALS['_xh']['method']->scalarval();
                    $GLOBALS['_xh']['params'] = $GLOBALS['_xh']['params']->me['array'];
                    $GLOBALS['_xh']['id'] = php_jsonrpc_decode($GLOBALS['_xh']['id']);
                }
                return true;
            }
        }
        else
        {
            return false;
        }
    }

    /**
    * Parse a json string, expected to be in json-rpc response format.
    * @access private
    * @todo checks missing:
    *       - no extra members in response
    *       - no extra members in error struct
    *       - resp. ID validation
    */
    function jsonrpc_parse_resp($data, $return_phpvals=false, $use_extension=false, $src_encoding='')
    {
        $GLOBALS['_xh']['isf']=0;
        $GLOBALS['_xh']['isf_reason']='';
        if ($return_phpvals && $use_extension)
        {
            $ok = json_parse_native($data);
        }
        else
        {
            $ok = json_parse($data, $return_phpvals, $src_encoding);
        }
        if ($ok)
        {
            if (!$return_phpvals)
            {
                $GLOBALS['_xh']['value'] = @$GLOBALS['_xh']['value']->me['struct'];
            }
            if (!is_array($GLOBALS['_xh']['value']) || !array_key_exists('result', $GLOBALS['_xh']['value'])
                || !array_key_exists('error', $GLOBALS['_xh']['value']) || !array_key_exists('id', $GLOBALS['_xh']['value']))
            {
                //$GLOBALS['_xh']['isf'] = 2;
                $GLOBALS['_xh']['isf_reason'] = 'JSON parsing did not return correct jsonrpc response object';
                return false;
            }
            if (!$return_phpvals)
            {
                $d_error = php_jsonrpc_decode($GLOBALS['_xh']['value']['error']);
                $GLOBALS['_xh']['value']['id'] = php_jsonrpc_decode($GLOBALS['_xh']['value']['id']);
            }
            else
            {
                $d_error = $GLOBALS['_xh']['value']['error'];
            }
            $GLOBALS['_xh']['id'] = $GLOBALS['_xh']['value']['id'];
            if ($d_error != null)
            {
                $GLOBALS['_xh']['isf'] = 1;

                //$GLOBALS['_xh']['value'] = $d_error;
                if (is_array($d_error) && array_key_exists('faultCode', $d_error)
                    && array_key_exists('faultString', $d_error))
                {
                    if($d_error['faultCode'] == 0)
                    {
                        // FAULT returned, errno needs to reflect that
                        $d_error['faultCode'] = -1;
                    }
                    $GLOBALS['_xh']['value'] = $d_error;
                }
                // NB: what about jsonrpc servers that do NOT respect
                // the faultCode/faultString convention???
                // we force the error into a string. regardless of type...
                else //if (is_string($GLOBALS['_xh']['value']))
                {
                    if ($return_phpvals)
                    {
                        $GLOBALS['_xh']['value'] = array('faultCode' => -1, 'faultString' => var_export($GLOBALS['_xh']['value']['error'], true));
                    }
                    else
                    {
                        $GLOBALS['_xh']['value'] = array('faultCode' => -1, 'faultString' => serialize_jsonrpcval($GLOBALS['_xh']['value']['error']));
                    }
                }

            }
            else
            {
                $GLOBALS['_xh']['value'] = $GLOBALS['_xh']['value']['result'];
            }
            return true;

        }
        else
        {
            return false;
        }
    }

    class jsonrpc_client extends xmlrpc_client
    {
        // by default, no multicall exists for JSON-RPC, so do not try it
        var $no_multicall = true;
        // default return type of calls to json-rpc servers: jsonrpcvals
        var $return_type = 'jsonrpcvals';

        /*
        function jsonrpc_client($path, $server='', $port='', $method='')
        {
            $this->xmlrpc_client($path, $server, $port, $method);
            // we need to override the list of std supported encodings, since
            // according to ECMA-262, the standard charset is UTF-16
            $this->accepted_charset_encodings = array('UTF-16', 'UTF-8', 'ISO-8859-1', 'US-ASCII');
        }
        */
    }


    class jsonrpcmsg extends xmlrpcmsg
    {
        var $id = null; // used to store request ID internally
        var $content_type = 'application/json';

        /**
        * @param string $meth the name of the method to invoke
        * @param array $pars array of parameters to be paased to the method (xmlrpcval objects)
        * @param mixed $id the id of the jsonrpc request
        */
        function jsonrpcmsg($meth, $pars=0, $id=null)
        {
            // NB: a NULL id is allowed and has a very definite meaning!
            $this->id = $id;
            $this->xmlrpcmsg($meth, $pars);
        }

        /**
        * @access private
        */
        function createPayload($charset_encoding='')
        {
            if ($charset_encoding != '')
                $this->content_type = 'application/json; charset=' . $charset_encoding;
            else
                $this->content_type = 'application/json';
            // @ todo: verify if all chars are allowed for method names or can
            // we just skip the js encoding on it?
            $this->payload = "{\n\"method\": \"" . json_encode_entities($this->methodname, '', $charset_encoding) . "\",\n\"params\": [ ";
            for($i = 0; $i < sizeof($this->params); $i++)
            {
                $p = $this->params[$i];
                // MB: we try to force serialization as json even though the object
                // param might be a plain xmlrpcval object.
                // This way we do not need to override addParam, aren't we lazy?
                $this->payload .= "\n  " . serialize_jsonrpcval($p, $charset_encoding) .
                ",";
            }
            $this->payload = substr($this->payload, 0, -1) . "\n],\n\"id\": ";
            switch (true)
            {
              case $this->id === null:
                  $this->payload .= 'null';
                  break;
              case is_string($this->id):
                  $this->payload .= '"'.json_encode_entities($this->id, '', $charset_encoding).'"';
                  break;
              case is_bool($this->id):
                  $this->payload .= ($this->id ? 'true' : 'false');
                  break;
              default:
                $this->payload .= $this->id;
            }
            $this->payload .= "\n}\n";
        }

        /**
        * Parse the jsonrpc response contained in the string $data and return a jsonrpcresp object.
        * @param string $data the xmlrpc response, eventually including http headers
        * @param bool $headers_processed when true prevents parsing HTTP headers for interpretation of content-encoding and conseuqent decoding
        * @param string $return_type decides return type, i.e. content of response->value(). Either 'xmlrpcvals', 'xml' or 'phpvals'
        * @return jsonrpcresp
        * @access private
        */
        function &parseResponse($data='', $headers_processed=false, $return_type='jsonrpcvals')
        {
            if($this->debug)
            {
                print "<PRE>---GOT---\n" . htmlentities($data) . "\n---END---\n</PRE>";
            }

            if($data == '')
            {
                error_log('XML-RPC: jsonrpcmsg::parseResponse: no response received from server.');
                $r = new jsonrpcresp(0, $GLOBALS['xmlrpcerr']['no_data'], $GLOBALS['xmlrpcstr']['no_data']);
                return $r;
            }

            $GLOBALS['_xh']=array();

            $raw_data = $data;
            // parse the HTTP headers of the response, if present, and separate them from data
            if(substr($data, 0, 4) == 'HTTP')
            {
                $r =& $this->parseResponseHeaders($data, $headers_processed);
                if ($r)
                {
                    // parent class implementation of parseResponseHeaders returns in case
                    // of error an object of the wrong type: recode it into correct object
                    $rj = new jsonrpcresp(0, $r->faultCode(), $r->faultString());
                    $rj->raw_data = $data;
                    return $rj;
                }
            }
            else
            {
                $GLOBALS['_xh']['headers'] = array();
                $GLOBALS['_xh']['cookies'] = array();
            }

            if($this->debug)
            {
                $start = strpos($data, '/* SERVER DEBUG INFO (BASE64 ENCODED):');
                if ($start !== false)
                {
                    $start += strlen('/* SERVER DEBUG INFO (BASE64 ENCODED):');
                    $end = strpos($data, '*/', $start);
                    $comments = substr($data, $start, $end-$start);
                    print "<PRE>---SERVER DEBUG INFO (DECODED) ---\n\t".htmlentities(str_replace("\n", "\n\t", base64_decode($comments)))."\n---END---\n</PRE>";
                }
            }

            // be tolerant of extra whitespace in response body
            $data = trim($data);

            // be tolerant of junk after methodResponse (e.g. javascript ads automatically inserted by free hosts)
            $end = strrpos($data, '}');
            if ($end)
            {
                $data = substr($data, 0, $end+1);
            }
            // if user wants back raw json, give it to him
            if ($return_type == 'json')
            {
                $r = new jsonrpcresp($data, 0, '', 'json');
                $r->hdrs = $GLOBALS['_xh']['headers'];
                $r->_cookies = $GLOBALS['_xh']['cookies'];
                $r->raw_data = $raw_data;
                return $r;
            }

            // @todo shall we try to check for non-unicode json received ???

            if (!jsonrpc_parse_resp($data, $return_type=='phpvals'))
            {
                if ($this->debug)
                {
                    /// @todo echo something for user?
                }

                $r = new jsonrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'],
                    $GLOBALS['xmlrpcstr']['invalid_return'] . ' ' . $GLOBALS['_xh']['isf_reason']);
            }
            //elseif ($return_type == 'jsonrpcvals' && !is_object($GLOBALS['_xh']['value']))
            //{
                // then something odd has happened
                // and it's time to generate a client side error
                // indicating something odd went on
            //    $r = & new jsonrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'],
            //        $GLOBALS['xmlrpcstr']['invalid_return']);
            //}
            else
            {
                $v = $GLOBALS['_xh']['value'];

                if ($this->debug)
                {
                    print "<PRE>---PARSED---\n" ;
                    var_export($v);
                    print "\n---END---</PRE>";
                }

                if($GLOBALS['_xh']['isf'])
                {
                    $r = new jsonrpcresp(0, $v['faultCode'], $v['faultString']);
                }
                else
                {
                    $r = new jsonrpcresp($v, 0, '', $return_type);
                }
                $r->id = $GLOBALS['_xh']['id'];
            }

            $r->hdrs = $GLOBALS['_xh']['headers'];
            $r->_cookies = $GLOBALS['_xh']['cookies'];
            $r->raw_data = $raw_data;
            return $r;
        }
    }

    class jsonrpcresp extends xmlrpcresp
    {
        var $content_type = 'application/json'; // NB: forces us to send US-ASCII over http
        var $id = null;

        /// @todo override creator, to set proper valtyp and id!

        /**
        * Returns json representation of the response.
        * @param string $charset_encoding the charset to be used for serialization. if null, US-ASCII is assumed
        * @return string the json representation of the response
        * @access public
        */
        function serialize($charset_encoding='')
        {
            if ($charset_encoding != '')
                $this->content_type = 'application/json; charset=' . $charset_encoding;
            else
                $this->content_type = 'application/json';
            $this->payload = serialize_jsonrpcresp($this, $this->id, $charset_encoding);
            return $this->payload;
        }

    }

    class jsonrpcval extends xmlrpcval
    {
        /**
        * Returns json representation of the value.
        * @param string $charset_encoding the charset to be used for serialization. if null, US-ASCII is assumed
        * @return string
        * @access public
        */
        function serialize($charset_encoding='')
        {
            return serialize_jsonrpcval($this, $charset_encoding);
        }
    }

    /**
    * Takes a json value in PHP jsonrpcval object format
    * and translates it into native PHP types.
    *
    * @param jsonrpcval $jsonrpc_val
    * @param array $options if 'decode_php_objs' is set in the options array, jsonrpc objects can be decoded into php objects
    * @return mixed
    * @access public
    */
    function php_jsonrpc_decode($jsonrpc_val, $options=array())
    {
        $kind = $jsonrpc_val->kindOf();

        if($kind == 'scalar')
        {
            return $jsonrpc_val->scalarval();
        }
        elseif($kind == 'array')
        {
            $size = $jsonrpc_val->arraysize();
            $arr = array();

            for($i = 0; $i < $size; $i++)
            {
                $arr[] = php_jsonrpc_decode($jsonrpc_val->arraymem($i), $options);
            }
            return $arr;
        }
        elseif($kind == 'struct')
        {
            $jsonrpc_val->structreset();
            // If user said so, try to rebuild php objects for specific struct vals.
            /// @todo should we raise a warning for class not found?
            // shall we check for proper subclass of xmlrpcval instead of
            // presence of _php_class to detect what we can do?
            if (in_array('decode_php_objs', $options))
            {
                if( $jsonrpc_val->_php_class != ''
                    && class_exists($jsonrpc_val->_php_class))
                {
                    $obj = @new $jsonrpc_val->_php_class;
                }
                else
                {
                    $obj = new stdClass();
                }
                while(list($key,$value) = $jsonrpc_val->structeach())
                {
                    $obj->$key = php_jsonrpc_decode($value, $options);
                }
                return $obj;
            }
            else
            {
                $arr = array();
                while(list($key,$value) = $jsonrpc_val->structeach())
                {
                    $arr[$key] = php_jsonrpc_decode($value, $options);
                }
                return $arr;
            }
        }
    }

    /**
    * Takes native php types and encodes them into jsonrpc PHP object format.
    * It will not re-encode jsonrpcval objects.
    *
    * @param mixed $php_val the value to be converted into a jsonrpcval object
    * @param array $options    can include 'encode_php_objs'
    * @return jsonrpcval
    * @access public
    */
    function &php_jsonrpc_encode($php_val, $options='')
    {
        $type = gettype($php_val);

        switch($type)
        {
            case 'string':
                $jsonrpc_val = new jsonrpcval($php_val, $GLOBALS['xmlrpcString']);
                break;
            case 'integer':
                $jsonrpc_val = new jsonrpcval($php_val, $GLOBALS['xmlrpcInt']);
                break;
            case 'double':
                $jsonrpc_val = new jsonrpcval($php_val, $GLOBALS['xmlrpcDouble']);
                break;
            case 'boolean':
                $jsonrpc_val = new jsonrpcval($php_val, $GLOBALS['xmlrpcBoolean']);
                break;
            case 'resource': // for compat with php json extension...
            case 'NULL':
                $jsonrpc_val = new jsonrpcval($php_val, $GLOBALS['xmlrpcNull']);
                break;
            case 'array':
                // PHP arrays can be encoded to either objects or arrays,
                // depending on wheter they are hashes or plain 0..n integer indexed
                // A shorter one-liner would be
                // $tmp = array_diff(array_keys($php_val), range(0, count($php_val)-1));
                // but execution time skyrockets!
                $j = 0;
                $arr = array();
                $ko = false;
                foreach($php_val as $key => $val)
                {
                    $arr[$key] =& php_jsonrpc_encode($val, $options);
                    if(!$ko && $key !== $j)
                    {
                        $ko = true;
                    }
                    $j++;
                }
                if($ko)
                {
                    $jsonrpc_val = new jsonrpcval($arr, $GLOBALS['xmlrpcStruct']);
                }
                else
                {
                    $jsonrpc_val = new jsonrpcval($arr, $GLOBALS['xmlrpcArray']);
                }
                break;
            case 'object':
                if($php_val instanceof jsonrpcval)
                {
                    $jsonrpc_val = $php_val;
                }
                else
                {
                    $arr = array();
                    while(list($k,$v) = each($php_val))
                    {
                        $arr[$k] = php_jsonrpc_encode($v, $options);
                    }
                    $jsonrpc_val = new jsonrpcval($arr, $GLOBALS['xmlrpcStruct']);
                    if (in_array('encode_php_objs', $options))
                    {
                        // let's save original class name into xmlrpcval:
                        // might be useful later on...
                        $jsonrpc_val->_php_class = get_class($php_val);
                    }
                }
                break;
            // catch "user function", "unknown type"
            default:
                $jsonrpc_val = new jsonrpcval();
                break;
            }
            return $jsonrpc_val;
    }

    /**
    * Convert the json representation of a jsonrpc method call, jsonrpc method response
    * or single json value into the appropriate object (a.k.a. deserialize).
    * Please note that there is no way to distinguish the serialized representation
    * of a single json val of type object which has the 3 appropriate members from
    * the serialization of a method call or method response.
    * In such a case, the function will return a jsonrpcresp or jsonrpcmsg
    * @param string $json_val
    * @param array $options
    * @return mixed false on error, or an instance of jsonrpcval, jsonrpcresp or jsonrpcmsg
    * @access public
    * @todo add options controlling character set encodings
    */
    function php_jsonrpc_decode_json($json_val, $options=array())
    {
        $src_encoding = array_key_exists('src_encoding', $options) ? $options['src_encoding'] : $GLOBALS['xmlrpc_defencoding'];
        $dest_encoding = array_key_exists('dest_encoding', $options) ? $options['dest_encoding'] : $GLOBALS['xmlrpc_internalencoding'];

        //$GLOBALS['_xh'] = array();
        $GLOBALS['_xh']['isf'] = 0;
        if (!json_parse($json_val, false, $src_encoding, $dest_encoding))
        {
            error_log($GLOBALS['_xh']['isf_reason']);
            return false;
        }
        else
        {
            $val = $GLOBALS['_xh']['value']; // shortcut
            if ($GLOBALS['_xh']['value']->kindOf() == 'struct')
            {
                if ($GLOBALS['_xh']['value']->structSize() == 3)
                {
                    if ($GLOBALS['_xh']['value']->structMemExists('method') &&
                        $GLOBALS['_xh']['value']->structMemExists('params') &&
                        $GLOBALS['_xh']['value']->structMemExists('id'))
                    {
                        /// @todo we do not check for correct type of 'method', 'params' struct members...
                        $method = $GLOBALS['_xh']['value']->structMem('method');
                        $msg = new jsonrpcmsg($method->scalarval(), null, php_jsonrpc_decode($GLOBALS['_xh']['value']->structMem('id')));
                        $params = $GLOBALS['_xh']['value']->structMem('params');
                        for($i = 0; $i < $params->arraySize(); ++$i)
                        {
                             $msg->addparam($params->arrayMem($i));
                        }
                        return $msg;
                    }
                    else
                    if ($GLOBALS['_xh']['value']->structMemExists('result') &&
                        $GLOBALS['_xh']['value']->structMemExists('error') &&
                        $GLOBALS['_xh']['value']->structMemExists('id'))
                    {
                        $id = php_jsonrpc_decode($GLOBALS['_xh']['value']->structMem('id'));
                        $err = php_jsonrpc_decode($GLOBALS['_xh']['value']->structMem('error'));
                        if ($err == null)
                        {
                            $resp = new jsonrpcresp($GLOBALS['_xh']['value']->structMem('result'));
                        }
                        else
                        {
                            if (is_array($err) && array_key_exists('faultCode', $err)
                                && array_key_exists('faultString', $err))
                            {
                                if($err['faultCode'] == 0)
                                {
                                    // FAULT returned, errno needs to reflect that
                                    $err['faultCode'] = -1;
                                }
                            }
                            // NB: what about jsonrpc servers that do NOT respect
                            // the faultCode/faultString convention???
                            // we force the error into a string. regardless of type...
                            else //if (is_string($GLOBALS['_xh']['value']))
                            {
                                $err = array('faultCode' => -1, 'faultString' => serialize_jsonrpcval($GLOBALS['_xh']['value']->structMem('error')));
                            }
                            $resp = new jsonrpcresp(0, $err['faultCode'], $err['faultString']);
                        }
                        $resp->id = $id;
                        return $resp;
                    }
                }
            }
            // not a request msg nor a response: a plain jsonrpcval obj
            return $GLOBALS['_xh']['value'];
        }
    }

    /**
    * Serialize a jsonrpcresp (or xmlrpcresp) as json.
    * Moved outside of the corresponding class to ease multi-serialization of
    * xmlrpcresp objects
    * @param xmlrpcresp or jsonrpcresp $resp
    * @param mixed $id
    * @return string
    * @access private
    */
    function serialize_jsonrpcresp($resp, $id=null, $charset_encoding='')
    {
        $result = "{\n\"id\": ";
        switch (true)
        {
          case $id === null:
              $result .= 'null';
              break;
          case is_string($id):
              $result .= '"'.json_encode_entities($id, '', $charset_encoding).'"';
              break;
          case is_bool($id):
              $result .= ($id ? 'true' : 'false');
              break;
          default:
            $result .= $id;
        }
        $result .= ", ";
        if($resp->errno)
        {
            // let non-ASCII response messages be tolerated by clients
            // by encoding non ascii chars
            $result .= "\"error\": { \"faultCode\": " . $resp->errno . ", \"faultString\": \"" . json_encode_entities($resp->errstr, null, $charset_encoding) . "\" }, \"result\": null";
        }
        else
        {
            if(!is_object($resp->val) || !($resp->val instanceof xmlrpcval))
            {
                if (is_string($resp->val) && $resp->valtyp == 'json')
                {
                    $result .= "\"error\": null, \"result\": " . $resp->val;
                }
                else
                {
                    /// @todo try to build something serializable?
                    die('cannot serialize jsonrpcresp objects whose content is native php values');
                }
            }
            else
            {
                $result .= "\"error\": null, \"result\": " .
                    serialize_jsonrpcval($resp->val, $charset_encoding);
            }
        }
        $result .= "\n}";
        return $result;
    }

    /**
    * Serialize a jsonrpcval (or xmlrpcval) as json.
    * Moved outside of the corresponding class to ease multi-serialization of
    * xmlrpcval objects
    * @param xmlrpcval or jsonrpcval $value
    * @string $charset_encoding
    * @access private
    */
    function serialize_jsonrpcval($value, $charset_encoding='')
    {
        reset($value->me);
        list($typ, $val) = each($value->me);

        $rs = '';
        switch(@$GLOBALS['xmlrpcTypes'][$typ])
        {
            case 1:
                switch($typ)
                {
                    case $GLOBALS['xmlrpcString']:
                        $rs .= '"' . json_encode_entities($val, null, $charset_encoding). '"';
                        break;
                    case $GLOBALS['xmlrpcI4']:
                    case $GLOBALS['xmlrpcInt']:
                        $rs .= (int)$val;
                        break;
                    case $GLOBALS['xmlrpcDateTime']:
                        // quote date as a json string.
                        // assumes date format is valid and will not break js...
                        $rs .=  '"' . $val . '"';
                        break;
                    case $GLOBALS['xmlrpcDouble']:
                        // add a .0 in case value is integer.
                        // This helps us carrying around floats in js, and keep them separated from ints
                        $sval = strval((double)$val); // convert to string
                        if (strpos($sval, '.') !== false || strpos($sval, 'e') !== false)
                        {
                            $rs .= $sval;
                        }
                        else
                        {
                            $rs .= $val.'.0';
                        }
                        break;
                    case $GLOBALS['xmlrpcBoolean']:
                        $rs .= ($val ? 'true' : 'false');
                        break;
                    case $GLOBALS['xmlrpcBase64']:
                        // treat base 64 values as strings ???
                        $rs .= '"' . base64_encode($val) . '"';
                        break;
                    default:
                        $rs .= "null";
                }
                break;
            case 2:
                // array
                $rs .= "[";
                $len = sizeof($val);
                if ($len)
                {
                    for($i = 0; $i < $len; $i++)
                    {
                        $rs .= serialize_jsonrpcval($val[$i], $charset_encoding);
                        $rs .= ",";
                    }
                    $rs = substr($rs, 0, -1) . "]";
                }
                else
                {
                    $rs .= "]";
                }
                break;
            case 3:
                // struct
                //if ($value->_php_class)
                //{
                    /// @todo implement json-rpc extension for object serialization
                    //$rs.='<struct php_class="' . $this->_php_class . "\">\n";
                //}
                //else
                //{
                //}
                if (is_array($val)) {
                foreach($val as $key2 => $val2)
                {
                    $rs .= ',"'.json_encode_entities($key2, null, $charset_encoding).'":';
                    $rs .= serialize_jsonrpcval($val2, $charset_encoding);
                }
                }
                $rs = '{' . substr($rs, 1) . '}';
                break;
            default:
                break;
        }
        return $rs;
    }

?>