Server : Apache System : Linux indy02.toastserver.com 3.10.0-962.3.2.lve1.5.85.el7.x86_64 #1 SMP Thu Apr 18 15:18:36 UTC 2024 x86_64 User : palandch ( 1163) PHP Version : 7.1.33 Disable Function : NONE Directory : /home/palandch/www/core/model/modx/jsonrpc/ |
<?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; } ?>