Source: jsonrpc_lib.js

/**
 * JS-XMLRPC: Yet Another JSONRPC Library, in Javascript!
 *
 * ...as if the world needed it...
 *
 * FOR COMPLETE API DOCS, READ PHP-XMLRPC API DOCS. THE SAME API (almost) IS IMPLEMENTED HERE!
 *
 * Many thanks to Jan-Klaas Kollhof for JSOLAIT, and to the Yahoo YUI team, for providing the starting point for all of this
 *
 * @author G. Giunta
 * @copyright (c) 2006-2022 G. Giunta
 * @license code licensed under the BSD License: see LICENSE file
 *
 * KNOWN DIFFERENCES FROM PHP-XMLRPC:
 * + jsonrpc_parse_resp() defaults to native parsing
 *
 * @todo json parsing code in json_parse() - do we need it al all in this time and age?
 */
// Requires: xmlrpc_lib.js
import {
 base64_decode,
 base64_encode,
 htmlentities,
 var_export,
 xh,
 xmlrpc_client,
 xmlrpc_debug_log,
 xmlrpcerr,
 xmlrpcmsg,
 xmlrpcresp,
 xmlrpcstr,
 xmlrpcval
} from './xmlrpc_lib.js'
/**
 * @private
 * @todo add support for charset transcoding
 */
function json_encode_entities(data, src_encoding, dest_encoding)
{
 if (data == undefined) // catches case of data === null as well
 {
 return '';
 }
 return data.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\u002F/g, '\\/').replace(/\t/g, '\\t').replace(/\n/g, '\\n').replace(/\r/g, '\\r').replace(/\u0008/g, '\\b').replace(/\v/g, '\\v').replace(/\f/g, '\\f');
}
/**
 * @private
 */
function json_parse(data, return_jsvals = false, src_encoding = 'UTF-8', dest_encoding = 'ISO-8859-1')
{
 xh.isf_reason = 'non-native JSON parsing not yet implemented.';
 return false;
}
/**
 * @private
 */
function json_parse_native(data)
{
 try
 {
 var out = JSON.parse(data);
 xh.value = out;
 return true;
 }
 catch (e)
 {
 xh.isf_reason = 'JSON parsing failed';
 return false;
 }
}
/**
 * @private
 */
function jsonrpc_parse_resp(data, return_jsvals = false, use_native_parsing = true)
{
 xh.isf = 0;
 xh.isf_reason = '';
 if (use_native_parsing)
 {
 var ok = json_parse_native(data);
 // we encode js vals to jsonrpcvals later, if needed
 //if (!return_jsvals)
 //{
 // xh.value = jsonrpc_encode(xh.value);
 //}
 }
 else
 {
 var ok = json_parse(data, return_jsvals);
 }
 if (ok)
 {
 //if (!return_jsvals)
 //{
 // xh.value = xh.value.me;
 //}
 if (typeof(xh.value) !== 'object' || xh.value['result'] === undefined
 || xh.value['error'] === undefined || xh.value['id'] === undefined)
 {
 //xh.isf = 2;
 xh.isf_reason = 'JSON parsing did not return correct jsonrpc response object';
 return false;
 }
 //if (!return_jsvals)
 //{
 // var d_error = jsonrpc_decode(xh.value['error']);
 // xh.value['id'] = php_jsonrpc_decode(xh.value['id']);
 //}
 //else
 //{
 var d_error = xh.value['error'];
 //}
 xh.id = xh.value['id'];
 if (d_error != null)
 {
 xh.isf = 1;
 //xh.value = $d_error;
 if (typeof(d_error) === 'object' && d_error['faultCode'] !== undefined
 && d_error['faultString'] !== undefined)
 {
 if (d_error['faultCode'] == 0)
 {
 // FAULT returned, errno needs to reflect that
 d_error['faultCode'] = -1;
 }
 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(xh.value))
 {
 if (return_jsvals)
 {
 xh.value = {'faultCode': -1, 'faultString': var_export(xh.value['error'])};
 }
 else
 {
 xh.value = {'faultCode': -1, 'faultString': serialize_jsonrpcval(jsonrpc_encode(xh.value['error']))};
 }
 }
 }
 else
 {
 if (!return_jsvals)
 xh.value = jsonrpc_encode(xh.value['result']);
 else
 xh.value = xh.value['result'];
 }
 return true;
 }
 else
 {
 return false;
 }
}
// *****************************************************************************
/**
 * @constructor
 **/
export function jsonrpc_client(path, server, port, method)
{
 this.no_multicall = true; // by default, multicall is not supported in jsonrpc
 this.return_type = 'jsonrpcvals';
 this.init(path, server, port, method);
}
jsonrpc_client.prototype = new xmlrpc_client();
// *****************************************************************************
/**
 * @param {string} meth Name of the method to be invoked
 * @param {array} pars list of parameters for method call (jsonrpcval objects)
 * @param {any} id of method call. Either a string, number or boolean or null. NULL has a special meaning for json-rpc
 * @constructor
 */
export function jsonrpcmsg(meth, pars, id)
{
 this.id = null;
 /** @private **/
 this.params = []; // somehow needed for making this weird subclassing work
 /** @private **/
 this.content_type = 'application/json';
 if (id !== undefined)
 {
 this.id = id;
 }
 this.init(meth, pars);
}
// let jsonrpcresp inherit methods from xmlrpcresp
jsonrpcmsg.prototype = new xmlrpcmsg();
/**
 * @private
 */
jsonrpcmsg.prototype.parseResponse = function(data, headers_processed = false, return_type = 'jsonrpcvals')
{
 var headers = '';
 if (typeof(headers_processed) === 'string')
 {
 headers = headers_processed;
 headers_processed = true;
 }
 if (this.debug)
 {
 xmlrpc_debug_log('<PRE>---GOT---\n' + htmlentities(data) + '\n---END---\n</PRE>');
 }
 if (data == '')
 {
 xmlrpc_error_log('XML-RPC: jsonrpcmsg::parseResponse: no response received from server.');
 var r = new jsonrpcresp(0, xmlrpcerr['no_data'], xmlrpcstr['no_data']);
 return r;
 }
 xh.reset = {headers: [], cookies: []};
 var raw_data = data;
 // examining http headers: check first if given as second param to function
 if (headers != '')
 {
 var r = this.parseResponseHeaders(headers, true);
 }
 // else check if http headers given as part of complete html response
 else if (data.slice(0, 4) == 'HTTP')
 {
 // if it was so, remove them (or return an error response, if parsing fails)
 var r = this.ParseResponseHeaders(data, headers_processed);
 if (typeof(r) !== 'string')
 {
 r.raw_data = data;
 return r;
 }
 else
 {
 data = r;
 }
 }
 if (this.debug)
 {
 var start = data.indexOf('/* SERVER DEBUG INFO (BASE64 ENCODED):');
 if (start != -1)
 {
 start += 39; //new String('<!-- SERVER DEBUG INFO (BASE64 ENCODED):').length();
 var end = data.indexOf('*/', start);
 var comments = data.slice(start, end-1);
 xmlrpc_debug_log('<PRE>---SERVER DEBUG INFO (DECODED)---\n\t'+htmlentities(base64_decode(comments).replace(/\n/g, '\n\t'))+'\n---END---\n</PRE>');
 }
 }
 // be tolerant of extra whitespace in response body
 data = data.replace(/^\s/, '').replace(/\s$/, '');
 // be tolerant of junk after methodResponse (e.g. javascript ads automatically inserted by free hosts)
 var pos = data.lastIndexOf('}');
 if (pos>= 0)
 {
 data = data.slice(0, pos+17);
 }
 // if user wants back raw json, give it to him
 if (return_type == 'json')
 {
 var r = new jsonrpcresp(data, 0, '', 'json');
 r.hdrs = xh.headers;
 r._cookies = 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=='jsvals'))
 {
 if (this.debug)
 {
 /// @todo echo something for user?
 }
 var r = new jsonrpcresp(0, xmlrpcerr['invalid_return'],
 xmlrpcstr['invalid_return'] + ' ' + 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
 {
 var v = xh.value;
 if (this.debug)
 {
 xmlrpc_debug_log("<PRE>---PARSED---\n");
 xmlrpc_debug_log(var_export(v));
 xmlrpc_debug_log("\n---END---</PRE>");
 }
 if (xh.isf)
 {
 var r = new jsonrpcresp(0, v['faultCode'], v['faultString']);
 }
 else
 {
 var r = new jsonrpcresp(v, 0, '', return_type);
 }
 r.id = xh.id;
 }
 r.hdrs = xh.headers;
 r._cookies = xh.cookies;
 r.raw_data = raw_data;
 return r;
}
/**
 * @private
 */
jsonrpcmsg.prototype.createPayload = function(charset_encoding)
{
 /// @ 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(var i = 0; i < this.params.length; ++i)
 {
 // NB: 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(this.params[i], charset_encoding) + ',';
 }
 this.payload = this.payload.slice(0, -1) + '\n],\n"id": ' + (this.id == null ? 'null' : this.id) + '\n}\n';
}
// *****************************************************************************
/**
 * @constructor
 */
export function jsonrpcresp(val, fcode, fstr, valtyp)
{
 this.id = null;
 this.init(val, fcode, fstr, valtyp);
}
// let jsonrpcresp inherit methods, default values, from xmlrpcresp
jsonrpcresp.prototype = new xmlrpcresp();
/**
 * @private
 */
jsonrpcresp.prototype.serialize = function(charset_encoding)
{
 this.payload = serialize_jsonrpcresp(this, this.id, charset_encoding);
 return this.payload;
}
// *****************************************************************************
/**
 * Create a jsonrpcval object out of a plain javascript value
 * @param {any} val
 * @param {string} type Any valid json type name (lowercase). If null, 'string' is assumed
 * @constructor
 */
export function jsonrpcval(val, type)
{
 this.init(val, type);
}
// let jsonrpcval inherit from xmlrpcval
jsonrpcval.prototype = new xmlrpcval();
/**
 * @private
 */
jsonrpcval.prototype.serialize = function(charset_encoding)
{
 return serialize_jsonrpcval(this, charset_encoding);
}
// *****************************************************************************
/**
 * Takes a json value in jsonrpcval object format
 * and translates it into native javascript types.
 * @public
 **/
export function jsonrpc_decode(jsonrpc_val, options = [])
{
 switch(jsonrpc_val.kindOf())
 {
 case 'scalar':
 return jsonrpc_val.scalarVal();
 case 'array':
 var size = jsonrpc_val.arraySize();
 var arr = [];
 for(var i = 0; i < size; ++i)
 {
 arr[arr.length] = jsonrpc_decode(jsonrpc_val.arrayMem(i), options);
 }
 return arr;
 case 'struct':
 // If user said so, try to rebuild js 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 (options['decode_js_objs'] && jsonrpc_val._js_class != '')
 //&& class_exists($xmlrpc_val->_php_class)) /// @todo check if a class exists with given name
 {
 var obj = new jsonrpc_val._js_class;
 }
 else
 {
 var obj = {};
 }
 for(var key in jsonrpc_val.me)
 {
 obj[key] = jsonrpc_decode(jsonrpc_val.me[key], options);
 }
 return obj;
 case 'msg':
 var paramcount = jsonrpc_val.getNumParams();
 var arr = [];
 for(var i = 0; i < paramcount; ++i)
 {
 arr[arr.length] = jsonrpc_val(jsonrpc_val.getParam(i));
 }
 return arr;
 }
}
/**
 * Takes native javascript types and encodes them into jsonrpc object format.
 * It will not re-encode jsonrpcval objects.
 * @public
 **/
export function jsonrpc_encode(js_val, options)
{
 var type = typeof js_val;
 switch(type)
 {
 case 'string':
 //if ((options != undefined && options['auto_dates']) && js_val.search(/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/) != -1)
 // var xmlrpc_val = new xmlrpcval(js_val, 'dateTime.iso8601');
 //else
 var jsonrpc_val = new jsonrpcval(js_val, 'string');
 break;
 case 'number':
 /// @todo...
 var num = new Number(js_val);
 if (num == parseInt(num))
 {
 var jsonrpc_val = new jsonrpcval(js_val, 'int');
 }
 else //if (num == parseFloat(num))
 {
 var jsonrpc_val = new jsonrpcval(js_val, 'double');
 }
 //else
 //{
 // // ??? only NaN and Infinity can get here. Encode them as zero (double)...
 // var xmlrpc_val = new xmlrpcval(0, 'double');
 //}
 break;
 case 'boolean':
 var jsonrpc_val = new jsonrpcval(js_val, 'boolean');
 break;
 case 'object':
 // we should be able to use js_val instanceof Null, but FF refuses it...
 // nb: check nulls first, since they have no attributes
 if (js_val === null)
 {
 //if (options != undefined && options['null_extension'])
 //{
 var jsonrpc_val = new jsonrpcval(null, 'null');
 //}
 //else
 //{
 // var xmlrpc_val = new xmlrpcval();
 //}
 }
 else
 if (js_val.toJsonRpcVal)
 {
 var jsonrpc_val = js_val.toJsonRpcVal();
 }
 else
 if (js_val instanceof Array)
 {
 var arr = [];
 for(var i = 0; i < js_val.length; ++i)
 {
 arr[arr.length] = jsonrpc_encode(js_val[i], options);
 }
 var jsonrpc_val = new jsonrpcval(arr, 'array');
 }
 else
 // xmlrpcval acquired capability to do this on its own, declaring toXmlRpcVal()
 //if (js_val instanceof xmlrpcval)
 //{
 // var xmlrpc_val = js_val;
 //}
 //else
 {
 // generic js object. encode all members except functions
 var arr = {};
 for(var attr in js_val)
 {
 if (typeof js_val[attr] !== 'function')
 {
 arr[attr] = jsonrpc_encode(js_val[attr], options);
 }
 }
 var jsonrpc_val = new jsonrpcval(arr, 'struct');
 /*if (in_array('encode_php_objs', options))
 {
 // let's save original class name into xmlrpcval:
 // might be useful later on...
 $xmlrpc_val._php_class = get_class($php_val);
 }*/
 }
 break;
 // match 'function', 'undefined', ...
 default:
 // it has to return an empty object in case
 var 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 (deserialize)
 * @param {string} json_val
 * @param {object} options not used (yet)
 * @type false | jsonrpcresp | jsonrpcmsg | jsonrpcval
 * @public
 *
 * @bug cannot tell a jsonrpc object from a reponse/request, if the object contains
 * the same members
 **/
export function jsonrpc_decode_json(json_val, options)
{
 //if (typeof(options) === 'object')
 //{
 // src_encoding = options['src_encoding'] != undefined ? options['src_encoding'] : xmlrpc_defencoding;
 // dest_encoding = options['dest_encoding'] != undefined ? options['dest_encoding'] : xmlrpc_internalencoding;
 //}
 xh.reset = {};
 //xh.isf = 0;
 //if (!json_parse(json_val, false, src_encoding, dest_encoding))
 if (!json_parse_native(json_val))
 {
 xmlrpc_error_log(xh.isf_reason);
 return false;
 }
 else
 {
 if (typeof(xh.value) === 'object' )
 {
 if (/*xh.value.length == 3 &&*/ xh.value['result'] !== undefined
 && xh.value['error'] !== undefined && xh.value['id'] !== undefined)
 {
 // decoding a jsonrpc reponse. Check first for error case
 if (xh.value['error'] != null)
 {
 if (typeof(xh.value['error']) === 'object' && xh.value['error']['faultCode'] !== undefined
 && xh.value['error']['faultString'] !== undefined)
 {
 if (xh.value['error']['faultCode'] == 0)
 {
 // FAULT returned, errno needs to reflect that
 xh.value['error']['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(xh.value))
 {
 xh.value = {'faultCode': -1, 'faultString': var_export(xh.value['error'])};
 }
 var r = new jsonrpcresp(0, xh.value['faultCode'], xh.value['faultString']);
 }
 else
 {
 var r = new jsonrpcresp(jsonrpc_encode(xh.value['result']));
 }
 r.id = xh.value['id'];
 return r;
 }
 else if (/*xh.value.length == 3 &&*/ xh.value['method'] !== undefined
 && xh.value['params'] !== undefined && xh.value['id'] !== undefined)
 {
 var r = new jsonrpcmsg(xh.value['method'], null, xh.value['id']);
 for (var i = 0; i < xh.value['params'].length; i++)
 r.addParam(jsonrpc_encode(xh.value['params'][i]));
 return r;
 }
 //else
 // return new jsonrpcval(xh.value, 'struct');
 }
 //else
 // parsing ok, but not a response / request: it must be a plain jsonrpcval
 return jsonrpc_encode(xh.value);
 }
}
/**
 * Serialize a jsonrpcresp (or xmlrpcresp) as json.
 * @private
 */
function serialize_jsonrpcresp(resp, id, charset_encoding)
{
 var result = '{\n"id": ' + (id == undefined ? 'null' : id) + ', ';
 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 (typeof resp.val !== 'object' || !(resp.val instanceof xmlrpcval))
 {
 if (typeof resp.val === 'string' && 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.
 * @private
 */
function serialize_jsonrpcval(value, charset_encoding)
{
 var rs = '';
 switch(value.mytype)
 {
 case 1:
 rs += '"' + json_encode_entities(value.me, null, charset_encoding) + '"';
 break;
 case 4:
 if (isFinite(value.me))
 {
 rs += value.me.toFixed(); // as per Ecma-262, toFixed is better than toString...
 }
 else
 {
 rs += '0';
 }
 break;
 case 5:
 // add a .0 in case value is integer.
 // This helps us carrying around floats in js, and keep them separated from ints
 if (isFinite(value.me) && value.me !== null)
 {
 rs += value.me.toString();
 var num = new Number(value.me);
 if (num == parseInt(num))
 {
 rs += '.0';
 }
 }
 else
 {
 rs += '0';
 }
 break;
 case 6:
 rs += (value.me ? 'true' : 'false');
 break;
 case 7:
 rs += '"' + value.me + '"'; /// @todo add some date string validation ???
 break;
 case 8:
 // treat base 64 values as strings ???
 rs += '"' + base64_encode(value.me) + '"';
 break;
 case 9:
 rs += "null";
 break;
 case 2:
 // array
 rs += "[";
 var len = (value.me.length);
 if (len)
 {
 for(var i = 0; i < len-1; ++i)
 {
 rs += serialize_jsonrpcval(value.me[i], charset_encoding);
 rs += ",";
 }
 rs += serialize_jsonrpcval(value.me[i], charset_encoding);
 }
 rs += "]";
 break;
 case 3:
 // struct
 //if ($value->_php_class)
 //{
 // /// @todo implement json-rpc extension for object serialization
 // $rs.='<struct php_class="' . value._php_class . "\">\n";
 //}
 //else
 //{
 //}
 for(var val in value.me)
 {
 rs += ',"' + json_encode_entities(val, null, charset_encoding) + '":';
 rs += serialize_jsonrpcval(value.me[val], charset_encoding);
 }
 rs = '{' + rs.slice(1) + '}';
 break;
 }
 return rs;
}

AltStyle によって変換されたページ (->オリジナル) /