/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla XML-RPC Client component. * * The Initial Developer of the Original Code is * Digital Creations 2, Inc. * Portions created by the Initial Developer are Copyright (C) 2000 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Martijn Pieters (original author) * Samuel Sieb * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* * nsXmlRpcClient XPCOM component * Version: $Revision: 1.39 $ * * $Id: nsXmlRpcClient.js,v 1.39 2006/10/24 16:02:01 silver%warwickcompsoc.co.uk Exp $ */ /* * Constants */ const XMLRPCCLIENT_CONTRACTID = '@mozilla.org/xml-rpc/client;1'; const XMLRPCCLIENT_CID = Components.ID('{4d7d15c0-3747-4f7f-b6b3-792a5ea1a9aa}'); const XMLRPCCLIENT_IID = Components.interfaces.nsIXmlRpcClient; const XMLRPCFAULT_CONTRACTID = '@mozilla.org/xml-rpc/fault;1'; const XMLRPCFAULT_CID = Components.ID('{691cb864-0a7e-448c-98ee-4a7f359cf145}'); const XMLRPCFAULT_IID = Components.interfaces.nsIXmlRpcFault; const XMLHTTPREQUEST_CONTRACTID = '@mozilla.org/xmlextras/xmlhttprequest;1'; const NSICHANNEL = Components.interfaces.nsIChannel; const DEBUG = false; const DEBUGPARSE = false; const DOMNode = Components.interfaces.nsIDOMNode; /* * Class definitions */ /* The nsXmlRpcFault class constructor. */ function nsXmlRpcFault() {} /* the nsXmlRpcFault class def */ nsXmlRpcFault.prototype = { faultCode: 0, faultString: '', init: function(faultCode, faultString) { this.faultCode = faultCode; this.faultString = faultString; }, toString: function() { return ''; }, // nsISupports interface QueryInterface: function(iid) { if (!iid.equals(Components.interfaces.nsISupports) && !iid.equals(XMLRPCFAULT_IID)) throw Components.results.NS_ERROR_NO_INTERFACE; return this; } }; /* The nsXmlRpcClient class constructor. */ function nsXmlRpcClient() {} /* the nsXmlRpcClient class def */ nsXmlRpcClient.prototype = { _serverUrl: null, _useAuth: false, init: function(serverURL) { this._serverUrl = serverURL; this._encoding = "UTF-8"; }, setAuthentication: function(username, password){ if ((typeof username == "string") && (typeof password == "string")){ this._useAuth = true; this._username = username; this._password = password; } }, clearAuthentication: function(){ this._useAuth = false; }, setEncoding: function(encoding){ this._encoding = encoding; }, get serverUrl() { return this._serverUrl; }, // Internal copy of the status _status: null, _listener: null, asyncCall: function(listener, context, methodName, methodArgs, count) { debug('asyncCall'); // Check for call in progress. if (this._inProgress) return; //throw Components.Exception('Call in progress!'); // Check for the server URL; if (!this._serverUrl) throw Components.Exception('Not initialized'); this._inProgress = true; // Clear state. this._foundFault = false; this._passwordTried = false; this._result = null; this._fault = null; this._status = null; this._responseStatus = null; this._responseString = null; this._listener = listener; this._seenStart = false; this._context = context; debug('Arguments: ' + methodArgs); // Generate request body var xmlWriter = new XMLWriter(this._encoding); this._generateRequestBody(xmlWriter, methodName, methodArgs); var requestBody = xmlWriter.data; debug('Request: ' + requestBody); this.xmlhttp = Components.classes[XMLHTTPREQUEST_CONTRACTID] .createInstance(Components.interfaces.nsIXMLHttpRequest); if (this._useAuth) { this.xmlhttp.open('POST', this._serverUrl, true, this._username, this._password); } else { this.xmlhttp.open('POST', this._serverUrl); } this.xmlhttp.onload = this._onload; this.xmlhttp.onerror = this._onerror; this.xmlhttp.parent = this; this.xmlhttp.setRequestHeader('Content-Type','text/xml'); this.xmlhttp.send(requestBody); var chan = this.xmlhttp.channel.QueryInterface(NSICHANNEL); chan.notificationCallbacks = this; }, _onload: function(e) { var result; var parent = e.target.parent; parent._inProgress = false; parent._responseStatus = e.target.status; parent._responseString = e.target.statusText; if (!e.target.responseXML) { if (e.target.status) { try { parent._listener.onError(parent, parent._context, Components.results.NS_ERROR_FAILURE, 'Server returned status ' + e.target.status); } catch (ex) { debug('Exception in listener.onError: ' + ex); } } else { try { parent._listener.onError(parent, parent._context, Components.results.NS_ERROR_FAILURE, 'Unknown network error'); } catch (ex) { debug('Exception in listener.onError: ' + ex); } } return; } try { e.target.responseXML.normalize(); result = parent.parse(e.target.responseXML); } catch (ex) { try { parent._listener.onError(parent, parent._context, ex.result, ex.message); } catch (ex) { debug('Exception in listener.onError: ' + ex); } return; } if (parent._foundFault) { parent._fault = result; try { parent._listener.onFault(parent, parent._context, result); } catch(ex) { debug('Exception in listener.onFault: ' + ex); } } else { parent._result = result.value; try { parent._listener.onResult(parent, parent._context, result.value); } catch (ex) { debug('Exception in listener.onResult: ' + ex); } } }, _onerror: function(e) { var parent = e.target.parent; parent._inProgress = false; try { parent._listener.onError(parent, parent._context, Components.results.NS_ERROR_FAILURE, 'Unknown network error'); } catch (ex) { debug('Exception in listener.onError: ' + ex); } }, _foundFault: false, _fault: null, _result: null, _responseStatus: null, _responseString: null, get fault() { return this._fault; }, get result() { return this._result; }, get responseStatus() { return this._responseStatus; }, get responseString() { return this._responseString; }, /* Convenience. Create an appropriate XPCOM object for a given type */ INT: 1, BOOLEAN: 2, STRING: 3, DOUBLE: 4, DATETIME: 5, ARRAY: 6, STRUCT: 7, BASE64: 8, // Not part of nsIXmlRpcClient interface, internal use. createType: function(type, uuid) { const SUPPORTSID = '@mozilla.org/supports-'; switch(type) { case this.INT: uuid.value = Components.interfaces.nsISupportsPRInt32 return createInstance(SUPPORTSID + 'PRInt32;1', 'nsISupportsPRInt32'); case this.BOOLEAN: uuid.value = Components.interfaces.nsISupportsPRBool return createInstance(SUPPORTSID + 'PRBool;1', 'nsISupportsPRBool'); case this.STRING: uuid.value = Components.interfaces.nsISupportsCString return createInstance(SUPPORTSID + 'cstring;1', 'nsISupportsCString'); case this.DOUBLE: uuid.value = Components.interfaces.nsISupportsDouble return createInstance(SUPPORTSID + 'double;1', 'nsISupportsDouble'); case this.DATETIME: uuid.value = Components.interfaces.nsISupportsPRTime return createInstance(SUPPORTSID + 'PRTime;1', 'nsISupportsPRTime'); case this.ARRAY: uuid.value = Components.interfaces.nsISupportsArray return createInstance(SUPPORTSID + 'array;1', 'nsISupportsArray'); case this.STRUCT: uuid.value = Components.interfaces.nsIDictionary return createInstance('@mozilla.org/dictionary;1', 'nsIDictionary'); default: throw Components.Exception('Unsupported type'); } }, // nsISupports interface QueryInterface: function(iid) { if (!iid.equals(Components.interfaces.nsISupports) && !iid.equals(XMLRPCCLIENT_IID) && !iid.equals(Components.interfaces.nsIInterfaceRequestor)) throw Components.results.NS_ERROR_NO_INTERFACE; return this; }, // nsIInterfaceRequester interface getInterface: function(iid, result){ if (iid.equals(Components.interfaces.nsIAuthPrompt)){ return this; } Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE; return null; }, // nsIAuthPrompt interface _passwordTried: false, promptUsernameAndPassword: function(dialogTitle, text, passwordRealm, savePassword, user, pwd){ if (this._useAuth){ if (this._passwordTried){ return false; } user.value = this._username; pwd.value = this._password; this._passwordTried = true; return true; } return false; }, /* Generate the XML-RPC request body */ _generateRequestBody: function(writer, methodName, methodArgs) { writer.startElement('methodCall'); writer.startElement('methodName'); writer.write(methodName); writer.endElement('methodName'); writer.startElement('params'); for (var i = 0; i < methodArgs.length; i++) { writer.startElement('param'); this._generateArgumentBody(writer, methodArgs[i]); writer.endElement('param'); } writer.endElement('params'); writer.endElement('methodCall'); }, /* Write out a XML-RPC parameter value */ _generateArgumentBody: function(writer, obj) { writer.startElement('value'); var sType = this._typeOf(obj); switch (sType) { case 'PRUint8': case 'PRUint16': case 'PRInt16': case 'PRInt32': obj=obj.QueryInterface(Components.interfaces['nsISupports' + sType]); writer.startElement('i4'); writer.write(obj.toString()); writer.endElement('i4'); break; case 'PRBool': obj=obj.QueryInterface(Components.interfaces.nsISupportsPRBool); writer.startElement('boolean'); writer.write(obj.data ? '1' : '0'); writer.endElement('boolean'); break; case 'Char': case 'CString': obj=obj.QueryInterface(Components.interfaces['nsISupports' + sType]); writer.startElement('string'); writer.write(obj.toString()); writer.endElement('string'); break; case 'Float': case 'Double': obj=obj.QueryInterface(Components.interfaces['nsISupports' + sType]); writer.startElement('double'); writer.write(obj.toString()); writer.endElement('double'); break; case 'PRTime': obj = obj.QueryInterface( Components.interfaces.nsISupportsPRTime); var date = new Date(obj.data) writer.startElement('dateTime.iso8601'); writer.write(iso8601Format(date)); writer.endElement('dateTime.iso8601'); break; case 'InputStream': obj = obj.QueryInterface(Components.interfaces.nsIInputStream); obj = toScriptableStream(obj); writer.startElement('base64'); streamToBase64(obj, writer); writer.endElement('base64'); break; case 'Array': obj = obj.QueryInterface( Components.interfaces.nsISupportsArray); writer.startElement('array'); writer.startElement('data'); for (var i = 0; i < obj.Count(); i++) this._generateArgumentBody(writer, obj.GetElementAt(i)); writer.endElement('data'); writer.endElement('array'); break; case 'Dictionary': obj = obj.QueryInterface(Components.interfaces.nsIDictionary); writer.startElement('struct'); var keys = obj.getKeys({}); for (var k = 0; k < keys.length; k++) { writer.startElement('member'); writer.startElement('name'); writer.write(keys[k]); writer.endElement('name'); this._generateArgumentBody(writer, obj.getValue(keys[k])); writer.endElement('member'); } writer.endElement('struct'); break; default: throw Components.Exception('Unsupported argument', null, null, obj); } writer.endElement('value'); }, /* Determine type of a nsISupports primitive, array or dictionary. */ _typeOf: function(obj) { // XPConnect alows JS to pass in anything, because we are a regular // JS object to it. So we have to test rigorously. if (typeof obj != 'object') return 'Unknown'; // Anything else not nsISupports is not allowed. if (typeof obj.QueryInterface != 'function') return 'Unknown'; // Now we will have to eliminate by trying all possebilities. try { obj.QueryInterface(Components.interfaces.nsISupportsPRUint8); return 'PRUint8'; } catch(e) {} try { obj.QueryInterface(Components.interfaces.nsISupportsPRUint16); return 'PRUint16'; } catch(e) {} try { obj.QueryInterface(Components.interfaces.nsISupportsPRInt16); return 'PRInt16'; } catch(e) {} try { obj.QueryInterface(Components.interfaces.nsISupportsPRInt32); return 'PRInt32'; } catch(e) {} try { obj.QueryInterface(Components.interfaces.nsISupportsPRBool); return 'PRBool'; } catch(e) {} try { obj.QueryInterface(Components.interfaces.nsISupportsChar); return 'Char'; } catch(e) {} try { obj.QueryInterface(Components.interfaces.nsISupportsCString); return 'CString'; } catch(e) {} try { obj.QueryInterface(Components.interfaces.nsISupportsFloat); return 'Float'; } catch(e) {} try { obj.QueryInterface(Components.interfaces.nsISupportsDouble); return 'Double'; } catch(e) {} try { obj.QueryInterface(Components.interfaces.nsISupportsPRTime); return 'PRTime'; } catch(e) {} try { obj.QueryInterface(Components.interfaces.nsIInputStream); return 'InputStream'; } catch(e) {} try { obj.QueryInterface(Components.interfaces.nsISupportsArray); return 'Array'; } catch(e) {} try { obj.QueryInterface(Components.interfaces.nsIDictionary); return 'Dictionary'; } catch(e) {} // Not a supported type return 'Unknown'; }, // Response parsing state _valueStack: [], _currValue: null, _cdata: null, parse: function(doc) { var node = doc.firstChild; var result; if (node.nodeType == DOMNode.TEXT_NODE) node = node.nextSibling; if ((node.nodeType != DOMNode.ELEMENT_NODE) || (node.nodeName != 'methodResponse')) { throw Components.Exception('Expecting a methodResponse', null, null, doc); } node = node.firstChild; if (node.nodeType == DOMNode.TEXT_NODE) node = node.nextSibling; if (node.nodeType != DOMNode.ELEMENT_NODE) throw Components.Exception('Expecting a params or fault', null, null, doc); if (node.nodeName == 'params') { node = node.firstChild; if (node.nodeType == DOMNode.TEXT_NODE) node = node.nextSibling; if ((node.nodeType != DOMNode.ELEMENT_NODE) || (node.nodeName != 'param')) { throw Components.Exception('Expecting a param', null, null, doc); } result = this.parseValue(node.firstChild); } else if (node.nodeName == 'fault') { this._foundFault = true; result = this.parseFault(node.firstChild); } else { throw Components.Exception('Expecting a params or fault', null, null, doc); } debug('Parse finished'); return result; }, parseValue: function(node) { var cValue = new Value(); if (node && (node.nodeType == DOMNode.TEXT_NODE)) node = node.nextSibling; if (!node || (node.nodeType != DOMNode.ELEMENT_NODE) || (node.nodeName != 'value')) { throw Components.Exception('Expecting a value', null, null, node); } node = node.firstChild; if (!node) return cValue; if (node.nodeType == DOMNode.TEXT_NODE){ if (!node.nextSibling) { cValue.value = node.nodeValue; return cValue; } else { node = node.nextSibling; } } if (node.nodeType != DOMNode.ELEMENT_NODE) throw Components.Exception('Expecting a value type', null, null, node); switch (node.nodeName) { case 'string': cValue.value = this.parseString(node.firstChild); break; case 'i4': case 'int': cValue.type = this.INT; cValue.value = this.parseString(node.firstChild); break; case 'boolean': cValue.type = this.BOOLEAN; cValue.value = this.parseString(node.firstChild); break; case 'double': cValue.type = this.DOUBLE; cValue.value = this.parseString(node.firstChild); break; case 'dateTime.iso8601': cValue.type = this.DATETIME; cValue.value = this.parseString(node.firstChild); break; case 'base64': cValue.type = this.BASE64; cValue.value = this.parseString(node.firstChild); break; case 'struct': cValue.type = this.STRUCT; this.parseStruct(cValue, node.firstChild); break; case 'array': cValue.type = this.ARRAY; this.parseArray(cValue, node.firstChild); break; default: throw Components.Exception('Expecting a value type', null, null, node); } return cValue; }, parseString: function(node) { value = ''; while (node) { if (node.nodeType != DOMNode.TEXT_NODE) throw Components.Exception('Expecting a text node', null, null, node); value += node.nodeValue; node = node.nextSibling; } return value; }, parseStruct: function(struct, node) { while (node) { if (node.nodeType == DOMNode.TEXT_NODE) node = node.nextSibling; if (!node) return; if ((node.nodeType != DOMNode.ELEMENT_NODE) || (node.nodeName != 'member')) { throw Components.Exception('Expecting a member', null, null, node); } this.parseMember(struct, node.firstChild); node = node.nextSibling; } }, parseMember: function(struct, node) { var cValue; if (node.nodeType == DOMNode.TEXT_NODE) node = node.nextSibling; if (!node || (node.nodeType != DOMNode.ELEMENT_NODE) || (node.nodeName != 'name')) { throw Components.Exception('Expecting a name', null, null, node); } struct.name = this.parseString(node.firstChild); cValue = this.parseValue(node.nextSibling); struct.appendValue(cValue.value); }, parseArray: function(array, node) { if (node.nodeType == DOMNode.TEXT_NODE) node = node.nextSibling; if (!node || (node.nodeType != DOMNode.ELEMENT_NODE) || (node.nodeName != 'data')) { throw Components.Exception('Expecting array data', null, null, node); } for (node = node.firstChild; node; node = node.nextSibling) { if (node.nodeType == DOMNode.TEXT_NODE) continue; array.appendValue(this.parseValue(node).value); } }, parseFault: function(node) { var fault = createInstance(XMLRPCFAULT_CONTRACTID, 'nsIXmlRpcFault'); var cValue = this.parseValue(node); if ((cValue.type != this.STRUCT) || (!cValue.value.hasKey('faultCode')) || (!cValue.value.hasKey('faultString'))) { throw Components.Exception('Invalid fault', null, null, node); } fault.init(cValue.value.getValue('faultCode').data, cValue.value.getValue('faultString').data); return fault; } }; /* The XMLWriter class constructor */ function XMLWriter(encoding) { if (!encoding) encoding = "UTF-8"; this.data = ''; } /* The XMLWriter class def */ XMLWriter.prototype = { data: '', startElement: function(element) { this.data += '<' + element + '>'; }, endElement: function(element) { this.data += ''; }, write: function(text) { for (var i = 0; i < text.length; i++) { var c = text[i]; switch (c) { case '<': this.data += '<'; break; case '&': this.data += '&'; break; default: this.data += c; } } }, markup: function(text) { this.data += text } }; /* The Value class contructor */ function Value() { this.type = this.STRING; }; /* The Value class def */ Value.prototype = { INT: nsXmlRpcClient.prototype.INT, BOOLEAN: nsXmlRpcClient.prototype.BOOLEAN, STRING: nsXmlRpcClient.prototype.STRING, DOUBLE: nsXmlRpcClient.prototype.DOUBLE, DATETIME: nsXmlRpcClient.prototype.DATETIME, ARRAY: nsXmlRpcClient.prototype.ARRAY, STRUCT: nsXmlRpcClient.prototype.STRUCT, BASE64: nsXmlRpcClient.prototype.BASE64, _createType: nsXmlRpcClient.prototype.createType, name: null, _value: null, get value() { return this._value; }, set value(val) { // accepts [0-9]+ or x[0-9a-fA-F]+ and returns the character. function entityTrans(substr, code) { return String.fromCharCode("0" + code); } switch (this.type) { case this.STRING: val = val.replace(/&#([0-9]+);/g, entityTrans); val = val.replace(/&#(x[0-9a-fA-F]+);/g, entityTrans); val = val.replace(/</g, '<'); val = val.replace(/>/g, '>'); val = val.replace(/&/g, '&'); this._value.data = val; break; case this.BOOLEAN: this._value.data = (val == 1); break; case this.DATETIME: this._value.data = Date.UTC(val.slice(0, 4), val.slice(4, 6) - 1, val.slice(6, 8), val.slice(9, 11), val.slice(12, 14), val.slice(15)); break; case this.BASE64: this._value.data = base64ToString(val); break; default: this._value.data = val; } }, _type: null, get type() { return this._type; }, set type(type) { this._type = type; if (type == this.BASE64) this._value = this._createType(this.STRING, {}); else this._value = this._createType(type, {}); }, appendValue: function(val) { switch (this.type) { case this.ARRAY: this.value.AppendElement(val); break; case this.STRUCT: this.value.setValue(this.name, val); break; } } }; /* * Objects */ /* nsXmlRpcClient Module (for XPCOM registration) */ var nsXmlRpcClientModule = { registerSelf: function(compMgr, fileSpec, location, type) { compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar); compMgr.registerFactoryLocation(XMLRPCCLIENT_CID, 'XML-RPC Client JS component', XMLRPCCLIENT_CONTRACTID, fileSpec, location, type); compMgr.registerFactoryLocation(XMLRPCFAULT_CID, 'XML-RPC Fault JS component', XMLRPCFAULT_CONTRACTID, fileSpec, location, type); }, getClassObject: function(compMgr, cid, iid) { if (!cid.equals(XMLRPCCLIENT_CID) && !cid.equals(XMLRPCFAULT_CID)) throw Components.results.NS_ERROR_NO_INTERFACE; if (!iid.equals(Components.interfaces.nsIFactory)) throw Components.results.NS_ERROR_NOT_IMPLEMENTED; if (cid.equals(XMLRPCCLIENT_CID)) return nsXmlRpcClientFactory else return nsXmlRpcFaultFactory; }, canUnload: function(compMgr) { return true; } }; /* nsXmlRpcClient Class Factory */ var nsXmlRpcClientFactory = { createInstance: function(outer, iid) { if (outer != null) throw Components.results.NS_ERROR_NO_AGGREGATION; if (!iid.equals(XMLRPCCLIENT_IID) && !iid.equals(Components.interfaces.nsISupports)) throw Components.results.NS_ERROR_INVALID_ARG; return new nsXmlRpcClient(); } } /* nsXmlRpcFault Class Factory */ var nsXmlRpcFaultFactory = { createInstance: function(outer, iid) { if (outer != null) throw Components.results.NS_ERROR_NO_AGGREGATION; if (!iid.equals(XMLRPCFAULT_IID) && !iid.equals(Components.interfaces.nsISupports)) throw Components.results.NS_ERROR_INVALID_ARG; return new nsXmlRpcFault(); } } /* * Functions */ /* module initialisation */ function NSGetModule(comMgr, fileSpec) { return nsXmlRpcClientModule; } /* Create an instance of the given ContractID, with given interface */ function createInstance(contractId, intf) { return Components.classes[contractId] .createInstance(Components.interfaces[intf]); } /* Get a pointer to a service indicated by the ContractID, with given interface */ function getService(contractId, intf) { return Components.classes[contractId] .getService(Components.interfaces[intf]); } /* Convert an inputstream to a scriptable inputstream */ function toScriptableStream(input) { var SIStream = Components.Constructor( '@mozilla.org/scriptableinputstream;1', 'nsIScriptableInputStream', 'init'); return new SIStream(input); } /* format a Date object into a iso8601 datetime string, UTC time */ function iso8601Format(date) { var datetime = date.getUTCFullYear(); var month = String(date.getUTCMonth() + 1); datetime += (month.length == 1 ? '0' + month : month); var day = date.getUTCDate(); datetime += (day < 10 ? '0' + day : day); datetime += 'T'; var hour = date.getUTCHours(); datetime += (hour < 10 ? '0' + hour : hour) + ':'; var minutes = date.getUTCMinutes(); datetime += (minutes < 10 ? '0' + minutes : minutes) + ':'; var seconds = date.getUTCSeconds(); datetime += (seconds < 10 ? '0' + seconds : seconds); return datetime; } /* Convert a stream to Base64, writing it away to a string writer */ const BASE64CHUNK = 255; // Has to be dividable by 3!! function streamToBase64(stream, writer) { while (stream.available()) { var data = []; while (data.length < BASE64CHUNK && stream.available()) { var d = stream.read(1).charCodeAt(0); // reading a 0 results in NaN, compensate. data = data.concat(isNaN(d) ? 0 : d); } writer.write(toBase64(data)); } } /* Convert data (an array of integers) to a Base64 string. */ const toBase64Table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' + '0123456789+/'; const base64Pad = '='; function toBase64(data) { var result = ''; var length = data.length; var i; // Convert every three bytes to 4 ascii characters. for (i = 0; i < (length - 2); i += 3) { result += toBase64Table[data[i] >> 2]; result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)]; result += toBase64Table[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)]; result += toBase64Table[data[i+2] & 0x3f]; } // Convert the remaining 1 or 2 bytes, pad out to 4 characters. if (length%3) { i = length - (length%3); result += toBase64Table[data[i] >> 2]; if ((length%3) == 2) { result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)]; result += toBase64Table[(data[i+1] & 0x0f) << 2]; result += base64Pad; } else { result += toBase64Table[(data[i] & 0x03) << 4]; result += base64Pad + base64Pad; } } return result; } /* Convert Base64 data to a string */ const toBinaryTable = [ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63, 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1, -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1 ]; function base64ToString(data) { var result = ''; var leftbits = 0; // number of bits decoded, but yet to be appended var leftdata = 0; // bits decoded, but yet to be appended // Convert one by one. for (var i = 0; i < data.length; i++) { var c = toBinaryTable[data.charCodeAt(i) & 0x7f]; var padding = (data[i] == base64Pad); // Skip illegal characters and whitespace if (c == -1) continue; // Collect data into leftdata, update bitcount leftdata = (leftdata << 6) | c; leftbits += 6; // If we have 8 or more bits, append 8 bits to the result if (leftbits >= 8) { leftbits -= 8; // Append if not padding. if (!padding) result += String.fromCharCode((leftdata >> leftbits) & 0xff); leftdata &= (1 << leftbits) - 1; } } // If there are any bits left, the base64 string was corrupted if (leftbits) throw Components.Exception('Corrupted base64 string'); return result; } if (DEBUG) debug = function(msg) { dump(' -- XML-RPC client -- : ' + msg + '\n'); }; else debug = function() {} // vim:sw=4:sr:sta:et:sts: