/*! * Mimic (XML-RPC Client for JavaScript) v2.3 * Copyright (C) 2005-2013 Carlos Eduardo Goncalves (cadu.goncalves@gmail.com) * * Mimic is dual licensed under the MIT (http://opensource.org/licenses/mit-license.php) * and GPLv3 (http://opensource.org/licenses/gpl-3.0.html) licenses. */ /** * XmlRpc helper. */ function XmlRpc() { }; /** *

* XML-RPC document prolog. *

*/ XmlRpc.PROLOG = "\n"; /** *

* XML-RPC methodCall node template. *

*/ XmlRpc.REQUEST = "\n${METHOD}\n\n${DATA}\n"; /** *

* XML-RPC param node template. *

*/ XmlRpc.PARAM = "\n\n${DATA}\n\n"; /** *

* XML-RPC array node template. *

*/ XmlRpc.ARRAY = "\n\n${DATA}\n\n"; /** *

* XML-RPC struct node template. *

*/ XmlRpc.STRUCT = "\n${DATA}\n"; /** *

* XML-RPC member node template. *

*/ XmlRpc.MEMBER = "\n${DATA}\n"; /** *

* XML-RPC name node template. *

*/ XmlRpc.NAME = "${DATA}\n"; /** *

* XML-RPC value node template. *

*/ XmlRpc.VALUE = "\n${DATA}\n"; /** *

* XML-RPC scalar node template (int, i4, double, string, boolean, base64, * dateTime.iso8601). *

*/ XmlRpc.SCALAR = "<${TYPE}>${DATA}\n"; /** *

* Get the tag name used to represent a JavaScript object in the XMLRPC * protocol. *

* * @param data * A JavaScript object. * @return String with XMLRPC object type. */ XmlRpc.getDataTag = function(data) { try { // Vars var tag = typeof data; switch (tag.toLowerCase()) { case "number": tag = (Math.round(data) == data) ? "int" : "double"; break; case "object": if (data.constructor == Base64) { tag = "base64"; } else if (data.constructor == String) { tag = "string"; } else if (data.constructor == Boolean) { tag = "boolean"; } else if (data.constructor == Array) { tag = "array"; } else if (data.constructor == Date) { tag = "dateTime.iso8601"; } else if (data.constructor == Number) { tag = (Math.round(data) == data) ? "int" : "double"; } else { tag = "struct"; } break; } return tag; } catch (e) { return null; } }; /** *

* Get JavaScript object type represented by XMLRPC protocol tag. *

* * @param tag * A XMLRPC tag name. * @return A JavaScript object. */ XmlRpc.getTagData = function(tag) { // Vars var data = null; switch (tag) { case "struct": data = new Object(); break; case "array": data = new Array(); break; case "datetime.iso8601": data = new Date(); break; case "boolean": data = new Boolean(); break; case "int": case "i4": case "double": data = new Number(); break; case "string": data = new String(); break; case "base64": data = new Base64(); break; } return data; }; /** * XmlRpcRequest. * * @param url * Server url. * @param method * Server side method do call. */ function XmlRpcRequest(url, method) { this.serviceUrl = url; this.methodName = method; this.crossDomain = false; this.withCredentials = false; this.params = []; this.headers = {}; }; /** *

* Add a new request parameter. *

* * @param data * New parameter value. */ XmlRpcRequest.prototype.addParam = function(data) { // Vars var type = typeof data; switch (type.toLowerCase()) { case "function": return; case "object": if (!data.constructor.name){ return; } } this.params.push(data); }; /** *

* Clear all request parameters. *

* * @param data * New parameter value. */ XmlRpcRequest.prototype.clearParams = function() { this.params.splice(0, this.params.length); }; /** *

* Define HTTP header value. *

* * @param name * Header name. * @param data * Header value. Use to clear the header. */ XmlRpcRequest.prototype.setHeader = function(name, value) { if(value) { this.headers[name] = value; } else { delete this.headers[name]; } }; /** *

* Execute a synchronous XML-RPC request. *

* * @return XmlRpcResponse object. */ XmlRpcRequest.prototype.send = function() { // Vars var xml_params = "", i = 0, xml_call, xhr; // XMLRPC for (i = 0; i < this.params.length; i++) { xml_params += XmlRpc.PARAM.replace("${DATA}", this.marshal(this.params[i])); } xml_call = XmlRpc.REQUEST.replace("${METHOD}", this.methodName); xml_call = XmlRpc.PROLOG + xml_call.replace("${DATA}", xml_params); // XHR xhr = Builder.buildXHR(this.crossDomain); xhr.open("POST", this.serviceUrl, false); // HTTP headers for(i in this.headers) { if (this.headers.hasOwnProperty(i)) { xhr.setRequestHeader(i, this.headers[i]); } } // HTTP credentials if(this.withCredentials && "withCredentials" in xhr) { xhr.withCredentials = true; } xhr.send(Builder.buildDOM(xml_call)); return new XmlRpcResponse(xhr.responseXML); }; /** *

* Marshal request parameters. *

* * @param data * A request parameter. * @return String with XML-RPC element notation. */ XmlRpcRequest.prototype.marshal = function(data) { // Vars var type = XmlRpc.getDataTag(data), scalar_type = XmlRpc.SCALAR.replace(/\$\{TYPE\}/g, type), xml = "", value, i, member; switch (type) { case "struct": member = ""; for (i in data) { value = ""; value += XmlRpc.NAME.replace("${DATA}", i); value += XmlRpc.VALUE.replace("${DATA}", this.marshal(data[i])); member += XmlRpc.MEMBER.replace("${DATA}", value); } xml = XmlRpc.STRUCT.replace("${DATA}", member); break; case "array": value = ""; for (i = 0; i < data.length; i++) { value += XmlRpc.VALUE.replace("${DATA}", this.marshal(data[i])); } xml = XmlRpc.ARRAY.replace("${DATA}", value); break; case "dateTime.iso8601": xml = scalar_type.replace("${DATA}", data.toIso8601()); break; case "boolean": xml = scalar_type.replace("${DATA}", (data == true) ? 1 : 0); break; case "base64": xml = scalar_type.replace("${DATA}", data.encode()); break; default: xml = scalar_type.replace("${DATA}", data); break; } return xml; }; /** * XmlRpcResponse. * * @param xml * Response XML document. */ function XmlRpcResponse(xml) { this.xmlData = xml; }; /** *

* Indicate if response is a fault. *

* * @return Boolean flag indicating fault status. */ XmlRpcResponse.prototype.isFault = function() { return this.faultValue; }; /** *

* Parse XML response to JavaScript. *

* * @return JavaScript object parsed from XML-RPC document. */ XmlRpcResponse.prototype.parseXML = function() { // Vars var i, nodesLength; nodesLength = this.xmlData.childNodes.length; this.faultValue = undefined; this.currentIsName = false; this.propertyName = ""; this.params = []; for (i = 0; i < nodesLength; i++) { this.unmarshal(this.xmlData.childNodes[i], 0); } return this.params[0]; }; /** *

* Unmarshal response parameters. *

* * @param node * Current document node under processing. * @param parent * Current node' parent node. */ XmlRpcResponse.prototype.unmarshal = function(node, parent) { // Vars var obj, tag, i, nodesLength; if (node.nodeType == 1) { obj = null; tag = node.tagName.toLowerCase(); switch (tag) { case "fault": this.faultValue = true; break; case "name": this.currentIsName = true; break; default: obj = XmlRpc.getTagData(tag); break; } if (obj != null) { this.params.push(obj); if (tag == "struct" || tag == "array") { if (this.params.length > 1) { switch (XmlRpc.getDataTag(this.params[parent])) { case "struct": this.params[parent][this.propertyName] = this.params[this.params.length - 1]; break; case "array": this.params[parent].push(this.params[this.params.length - 1]); break; } } parent = this.params.length - 1; } } nodesLength = node.childNodes.length; for (i = 0; i < nodesLength; i++) { this.unmarshal(node.childNodes[i], parent); } } if ((node.nodeType == 3) && (/[^\t\n\r ]/.test(node.nodeValue))) { if (this.currentIsName == true) { this.propertyName = node.nodeValue; this.currentIsName = false; } else { switch (XmlRpc.getDataTag(this.params[this.params.length - 1])) { case "dateTime.iso8601": this.params[this.params.length - 1] = Date.fromIso8601(node.nodeValue); break; case "boolean": this.params[this.params.length - 1] = (node.nodeValue == "1") ? true : false; break; case "int": case "double": this.params[this.params.length - 1] = new Number(node.nodeValue); break; case "string": this.params[this.params.length - 1] = new String(node.nodeValue); break; case "base64": this.params[this.params.length - 1] = new Base64(node.nodeValue); break; } if (this.params.length > 1) { switch (XmlRpc.getDataTag(this.params[parent])) { case "struct": this.params[parent][this.propertyName] = this.params[this.params.length - 1]; break; case "array": this.params[parent].push(this.params[this.params.length - 1]); break; } } } } }; /** * Builder helper for W3C / ActiveX objects */ function Builder() { }; /** *

* Build a valid XMLHttpRequest object *

* * @param cors * Define if returned implementation must provide CORS (Cross-Origin Resource Sharing) support. * @return XMLHttpRequest object. */ Builder.buildXHR = function(cors) { if(cors) { return (typeof XDomainRequest != "undefined") ? new XDomainRequest() : new XMLHttpRequest(); } else { return (typeof XMLHttpRequest != "undefined") ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP"); } }; /** *

* Build a valid XML document from string markup. *

* * @param xml * Document markup. * @return XMLDocument object. */ Builder.buildDOM = function(xml) { // Vars var parser, names, i; if (typeof DOMParser != "undefined") { parser = new DOMParser(); return parser.parseFromString(xml, "text/xml"); } else { names = [ "Microsoft.XMLDOM", "MSXML2.DOMDocument", "MSXML.DOMDocument" ]; for (i = 0; i < names.length; i++) { try { parser = new ActiveXObject(names[i]); parser.loadXML(xml); return parser; } catch (e) { /* Ignore */ } } } return null; }; /** * Date extensions. */ /** *

* Convert a GMT date to ISO8601. *

* * @return String with an ISO8601 date. */ Date.prototype.toIso8601 = function() { // Vars var year = this.getYear(), month = this.getMonth() + 1, day = this.getDate(), time = this.toTimeString().substr(0, 8); // Normalization if (year < 1900) { year += 1900; } if (month < 10) { month = "0" + month; } if (day < 10) { day = "0" + day; } return year + month + day + "T" + time; }; /** *

* Convert ISO8601 date to GMT. *

* * @param value * ISO8601 date. * @return GMT date. */ Date.fromIso8601 = function(value) { // Vars var year = value.substr(0, 4), month = value.substr(4, 2), day = value.substr(6, 2), hour = value.substr(9, 2), minute = value.substr(12, 2), sec = value.substr(15, 2); return new Date(year, month - 1, day, hour, minute, sec, 0); }; /** * Base64 implementation. */ function Base64(value) { this.bytes = value; }; /** *

* Base64 characters map. *

*/ Base64.CHAR_MAP = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; /** *

* Encode the object bytes using base64 algorithm. *

* * @return Encoded string. */ Base64.prototype.encode = function() { if (typeof btoa == "function") { return btoa(this.bytes); } else { // Vars var _byte = [], _char = [], _result = [], j = 0, i = 0; for (i = 0; i < this.bytes.length; i += 3) { _byte[0] = this.bytes.charCodeAt(i); _byte[1] = this.bytes.charCodeAt(i + 1); _byte[2] = this.bytes.charCodeAt(i + 2); _char[0] = _byte[0] >> 2; _char[1] = ((_byte[0] & 3) << 4) | (_byte[1] >> 4); _char[2] = ((_byte[1] & 15) << 2) | (_byte[2] >> 6); _char[3] = _byte[2] & 63; if (isNaN(_byte[1])) { _char[2] = _char[3] = 64; } else if (isNaN(_byte[2])) { _char[3] = 64; } _result[j++] = Base64.CHAR_MAP.charAt(_char[0]) + Base64.CHAR_MAP.charAt(_char[1]) + Base64.CHAR_MAP.charAt(_char[2]) + Base64.CHAR_MAP.charAt(_char[3]); } return _result.join(""); } }; /** *

* Decode the object bytes using base64 algorithm. *

* * @return Decoded string. */ Base64.prototype.decode = function() { if (typeof atob == "function") { return atob(this.bytes); } else { // Vars var _byte = [], _char = [], _result = [], j = 0, i = 0; while ((this.bytes.length % 4) != 0) { this.bytes += "="; } for (i = 0; i < this.bytes.length; i += 4) { _char[0] = Base64.CHAR_MAP.indexOf(this.bytes.charAt(i)); _char[1] = Base64.CHAR_MAP.indexOf(this.bytes.charAt(i + 1)); _char[2] = Base64.CHAR_MAP.indexOf(this.bytes.charAt(i + 2)); _char[3] = Base64.CHAR_MAP.indexOf(this.bytes.charAt(i + 3)); _byte[0] = (_char[0] << 2) | (_char[1] >> 4); _byte[1] = ((_char[1] & 15) << 4) | (_char[2] >> 2); _byte[2] = ((_char[2] & 3) << 6) | _char[3]; _result[j++] = String.fromCharCode(_byte[0]); if (_char[2] != 64) { _result[j++] = String.fromCharCode(_byte[1]); } if (_char[3] != 64) { _result[j++] = String.fromCharCode(_byte[2]); } } return _result.join(""); } };