| /* ***** 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 <mj@digicool.com> (original author) |
| * Samuel Sieb <samuel@sieb.net> |
| * |
| * 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 '<XML-RPC Fault: (' + this.faultCode + ') ' + |
| this.faultString + '>'; |
| }, |
| |
| // 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) |
| 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 = '<?xml version="1.0" encoding="' + encoding + '"?>'; |
| } |
| |
| /* The XMLWriter class def */ |
| XMLWriter.prototype = { |
| data: '', |
| |
| startElement: function(element) { |
| this.data += '<' + element + '>'; |
| }, |
| |
| endElement: function(element) { |
| this.data += '</' + element + '>'; |
| }, |
| |
| 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: |