1040 lines
34 KiB
JavaScript
1040 lines
34 KiB
JavaScript
/* ***** 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)
|
|
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 = '<?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:
|