From 1d76c60c78083ca71b7c8ce9856cef52d0713a2a Mon Sep 17 00:00:00 2001 From: "mj%digicool.com" Date: Fri, 5 May 2000 06:06:34 +0000 Subject: [PATCH] New XML-RPC Client component. _NOT PART OF THE BUILD_ git-svn-id: svn://10.0.0.236/trunk@68360 18797224-902f-48f8-a5cc-f745e15eee43 --- mozilla/extensions/xml-rpc/Makefile.in | 32 + mozilla/extensions/xml-rpc/idl/MANIFEST_IDL | 16 + mozilla/extensions/xml-rpc/idl/Makefile.in | 35 + mozilla/extensions/xml-rpc/idl/makefile.win | 30 + .../extensions/xml-rpc/idl/nsIDictionary.idl | 83 + .../xml-rpc/idl/nsIXmlRpcClient.idl | 160 ++ .../xml-rpc/idl/nsIXmlRpcClientListener.idl | 66 + mozilla/extensions/xml-rpc/makefile.win | 27 + .../xml-rpc/src/MANIFEST_COMPONENTS | 15 + mozilla/extensions/xml-rpc/src/Makefile.in | 33 + mozilla/extensions/xml-rpc/src/makefile.win | 29 + .../extensions/xml-rpc/src/nsDictionary.js | 125 ++ .../extensions/xml-rpc/src/nsXmlRpcClient.js | 1433 +++++++++++++++++ 13 files changed, 2084 insertions(+) create mode 100644 mozilla/extensions/xml-rpc/Makefile.in create mode 100644 mozilla/extensions/xml-rpc/idl/MANIFEST_IDL create mode 100644 mozilla/extensions/xml-rpc/idl/Makefile.in create mode 100644 mozilla/extensions/xml-rpc/idl/makefile.win create mode 100644 mozilla/extensions/xml-rpc/idl/nsIDictionary.idl create mode 100644 mozilla/extensions/xml-rpc/idl/nsIXmlRpcClient.idl create mode 100644 mozilla/extensions/xml-rpc/idl/nsIXmlRpcClientListener.idl create mode 100644 mozilla/extensions/xml-rpc/makefile.win create mode 100644 mozilla/extensions/xml-rpc/src/MANIFEST_COMPONENTS create mode 100644 mozilla/extensions/xml-rpc/src/Makefile.in create mode 100644 mozilla/extensions/xml-rpc/src/makefile.win create mode 100644 mozilla/extensions/xml-rpc/src/nsDictionary.js create mode 100644 mozilla/extensions/xml-rpc/src/nsXmlRpcClient.js diff --git a/mozilla/extensions/xml-rpc/Makefile.in b/mozilla/extensions/xml-rpc/Makefile.in new file mode 100644 index 00000000000..f97e5448cd8 --- /dev/null +++ b/mozilla/extensions/xml-rpc/Makefile.in @@ -0,0 +1,32 @@ +# +# 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 and XPCOM Dictionary components. +# +# The Initial Developer of the Original Code is Digital Creations 2, Inc. +# Portions created by Digital Creations 2, Inc. are Copyright (C) 2000 Digital +# Creations 2, Inc. All Rights Reserved. +# +# Contributor(s): Martijn Pieters (original author) +# + +DEPTH = ../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +DIRS = \ + idl \ + src \ + $(NULL) + +include $(topsrcdir)/config/rules.mk + diff --git a/mozilla/extensions/xml-rpc/idl/MANIFEST_IDL b/mozilla/extensions/xml-rpc/idl/MANIFEST_IDL new file mode 100644 index 00000000000..5258464bf16 --- /dev/null +++ b/mozilla/extensions/xml-rpc/idl/MANIFEST_IDL @@ -0,0 +1,16 @@ +# 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 and XPCOM Dictionary components. +# +# The Initial Developer of the Original Code is Digital Creations 2, Inc. +# Portions created by Digital Creations 2, Inc. are Copyright (C) 2000 Digital +# Creations 2, Inc. All Rights Reserved. +# +# Contributor(s): Martijn Pieters (original author) +# + +nsIDictionary.idl +nsIXmlRpcClient.idl +nsIXmlRpcClientListener.idl diff --git a/mozilla/extensions/xml-rpc/idl/Makefile.in b/mozilla/extensions/xml-rpc/idl/Makefile.in new file mode 100644 index 00000000000..9a4d5ca0f09 --- /dev/null +++ b/mozilla/extensions/xml-rpc/idl/Makefile.in @@ -0,0 +1,35 @@ +# +# 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 and XPCOM Dictionary components. +# +# The Initial Developer of the Original Code is Digital Creations 2, Inc. +# Portions created by Digital Creations 2, Inc. are Copyright (C) 2000 Digital +# Creations 2, Inc. All Rights Reserved. +# +# Contributor(s): Martijn Pieters (original author) +# + +DEPTH = ../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = xml-rpc + +XPIDLSRCS = \ + nsIDictionary.idl \ + nsIXmlRpcClientListener.idl \ + nsIXmlRpcClient.idl \ + $(NULL) + +include $(topsrcdir)/config/rules.mk + diff --git a/mozilla/extensions/xml-rpc/idl/makefile.win b/mozilla/extensions/xml-rpc/idl/makefile.win new file mode 100644 index 00000000000..6abbfb69aa6 --- /dev/null +++ b/mozilla/extensions/xml-rpc/idl/makefile.win @@ -0,0 +1,30 @@ +# +# 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 and XPCOM Dictionary components. +# +# The Initial Developer of the Original Code is Digital Creations 2, Inc. +# Portions created by Digital Creations 2, Inc. are Copyright (C) 2000 Digital +# Creations 2, Inc. All Rights Reserved. +# +# Contributor(s): Martijn Pieters (original author) +# + +MODULE = xml-rpc + +DEPTH = ..\..\.. + +XPIDLSRCS = \ + .\nsIDictionary.idl \ + .\nsIXmlRpcClientListener.idl \ + .\nsIXmlRpcClient.idl \ + $(NULL) + +include <$(DEPTH)/config/rules.mak> + diff --git a/mozilla/extensions/xml-rpc/idl/nsIDictionary.idl b/mozilla/extensions/xml-rpc/idl/nsIDictionary.idl new file mode 100644 index 00000000000..cd164309e17 --- /dev/null +++ b/mozilla/extensions/xml-rpc/idl/nsIDictionary.idl @@ -0,0 +1,83 @@ +/* + * 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 XPCOM Dictionary. + * + * The Initial Developer of the Original Code is Digital Creations 2, Inc. + * Portions created by Digital Creations 2, Inc. are Copyright (C) 2000 Digital + * Creations 2, Inc. All Rights Reserved. + * + * Contributor(s): Martijn Pieters (original author) + */ + +/* + * Simple dictionary implementation. + * Version: $Revision: 1.1 $ + * + * $Id: nsIDictionary.idl,v 1.1 2000-05-05 06:06:31 mj%digicool.com Exp $ + */ + +#include "nsISupports.idl" + +/** + * A simple mutable table of objects, accessed by key. + */ +[scriptable, uuid(1dd0cb45-aea3-4a52-8b29-01429a542863)] +interface nsIDictionary: nsISupports { + /** + * Check if a given key is present in the dictionary. + * + * @param key Key to check for + * @return true if present, false if absent. + */ + boolean hasKey(in string key); + + /** + * Retrieve all keys in the dictionary. + * + * @return array of all keys, unsorted. + */ + void getKeys(out PRUint32 count, + [retval, array, size_is(count)] out string keys); + + /** + * Find the value indicated by the key. + * + * @param key The lookup key indicating the value. + * @return Value indicated by key. If the key doesn't exist, + * NS_ERROR_FAILURE will be returned. + */ + nsISupports getValue(in string key); + + /** + * Add the key-value pair to the dictionary. + * If the key is already present, replace the old value + * with the new. + * + * @param key The key by which the value can be accessed + * @param value The value to be stored. + */ + void setValue(in string key, in nsISupports value); + + /** + * Delete the indicated key-value pair. + * + * @param key The key indicating the pair to be removed. + * @return The removed value. If the key doesn't exist, + * NS_ERROR_FAILURE will be returned. + */ + nsISupports deleteValue(in string key); + + /** + * Delete all key-value pairs from the dictionary. + */ + void clear(); +}; + +// vim:sw=4:sr:sta:et:sts: diff --git a/mozilla/extensions/xml-rpc/idl/nsIXmlRpcClient.idl b/mozilla/extensions/xml-rpc/idl/nsIXmlRpcClient.idl new file mode 100644 index 00000000000..d67bbb0abcf --- /dev/null +++ b/mozilla/extensions/xml-rpc/idl/nsIXmlRpcClient.idl @@ -0,0 +1,160 @@ +/* + * 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 Digital Creations 2, Inc. are Copyright (C) 2000 Digital + * Creations 2, Inc. All Rights Reserved. + * + * Contributor(s): Martijn Pieters (original author) + */ + +/* + * XPCOM XML-RPC Client, interface definition. + * Version: $Revision: 1.1 $ + * + * $Id: nsIXmlRpcClient.idl,v 1.1 2000-05-05 06:06:31 mj%digicool.com Exp $ + */ + +#include "nsISupports.idl" +#include "nsIURL.idl" +#include "nsIXmlRpcClientListener.idl" + +// forward decl. +interface nsIXmlRpcFault; + +/** + * Simple XML-RPC client interface. + */ +[scriptable, uuid(37127241-1e6e-46aa-ba87-601d41bb47df)] +interface nsIXmlRpcClient: nsISupports { + /** + * Set server URL. Call this before using this object. + * + * @param serverURL URL of server side object on which methods should + * be called. + */ + void init(in string serverURL); + + /** + * The URL of the XML-RPC server + */ + readonly attribute nsIURL serverURL; + + /** + * Call remote method methodName with given arguments. + * + * Supported arguments are: + * nsISupportsPRUint8, nsISupportsPRUint16, + * nsISupportsPRInt16, nsISupportsPRInt32: + * nsISupportsPRBool: + * nsISupportsChar, nsISupportsString: + * nsISupportsFloat, nsISupportsDouble: + * nsISupportsPRTime: + * nsIInputStream: + * nsISupportsArray: + * nsIDictionary: + * + * Note that both nsISupportsArray and nsIDictionary can only hold any of + * the supported input types. + * + * Return value will be converted as follows: + * or : nsISupportsPRInt32 + * : nsISupportsPRBool + * : nsISupportsString + * : nsISupportsDouble + * : nsISupportsPRTime + * : nsISupportsString + * : nsISupportsArray + * : nsIDictionary + * + * s (server side errors) are indicated by returning + * NS_ERROR_FAILURE. Via nsIXPConnect::GetPendingException()->data a + * nsIXmlRpcFault object can be retreieved with more information on the + * fault. + * + * @param methodName Remote method to call. + * @param arguments Array of arguments to pass to remote method. + * @return Return value of remote method. + */ + nsISupports call(in string methodName, + [array, size_is(count)] in nsISupports arguments, in PRUint32 count); + + /** + * The asynchronous version of call. + * + * @param listener A nsIXmlRpcClientListener that will get notified + * of XML-RPC events. + * @param context A context to be passed on to the listener. + * @param methodName Remote method to call. + * @param arguments Array of arguments to pass to remote method. + * @return Return value of remote method. + */ + void asyncCall(in nsIXmlRpcClientListener listener, in nsISupports ctxt, + in string methodName, + [array, size_is(count)] in nsISupports arguments, in PRUint32 count); + + /** + * Wether or not a call is in progress + */ + readonly attribute boolean inProgress; + + /** + * The most recent XML-RPC fault from returned from this server. + * null if the last call didn't return an XML-RPC fault. + */ + readonly attribute nsIXmlRpcFault fault; + + /** + * The most recent XML-RPC call result returned from this server. + * null if the last call didn't return a valid result + */ + readonly attribute nsISupports result; + + /** + * The most recent HTTP status code returned from this server + * null if the server was unreachable or not yet contacted. + */ + readonly attribute unsigned long responseStatus; + readonly attribute unsigned long responseString; + + /** + * Convenience: return the correct nsISupportsPrimitive for a given XML-RPC + * type, or nsISupportsArray or nsIDictionary. 'base64' isn't supported. + * + * @param type One of the listed constants. + * @return Return an appropriate XPCOM object. + */ + nsISupports createType(in unsigned long type); + const unsigned long INT = 1; // nsISupportsPRInt32 + const unsigned long BOOLEAN = 2; // nsISupportsPRBool + const unsigned long STRING = 3; // nsISupportsString + const unsigned long DOUBLE = 4; // nsISupportsDouble + const unsigned long DATETIME = 5; // nsISupportsPRTime + const unsigned long ARRAY = 6; // nsISupportsArray + const unsigned long STRUCT = 7; // nsIDictionary +}; + +/** + * An XML-RPC exception. + * XML-RPC server fault codes are returned wrapped in this Access via + * nsIXPConnect::GetPendingException()->data + */ +[scriptable, uuid(691cb864-0a7e-448c-98ee-4a7f359cf145)] +interface nsIXmlRpcFault: nsISupports { + readonly attribute PRInt32 faultCode; + readonly attribute string faultString; + + void init(in PRInt32 faultCode, in string faultString); + + string toString(); +}; + +// vim:sw=4:sr:sta:et:sts: diff --git a/mozilla/extensions/xml-rpc/idl/nsIXmlRpcClientListener.idl b/mozilla/extensions/xml-rpc/idl/nsIXmlRpcClientListener.idl new file mode 100644 index 00000000000..6f6143bc292 --- /dev/null +++ b/mozilla/extensions/xml-rpc/idl/nsIXmlRpcClientListener.idl @@ -0,0 +1,66 @@ +/* + * 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 Digital Creations 2, Inc. are Copyright (C) 2000 Digital + * Creations 2, Inc. All Rights Reserved. + * + * Contributor(s): Martijn Pieters (original author) + */ + +/* + * XPCOM XML-RPC Client listener, interface definition. + * Version: $Revision: 1.1 $ + * + * $Id: nsIXmlRpcClientListener.idl,v 1.1 2000-05-05 06:06:31 mj%digicool.com Exp $ + */ + +#include "nsISupports.idl" + +// forward decl. +interface nsIXmlRpcClient; +interface nsIXmlRpcFault; + +[scriptable, uuid(27e60cd8-6d63-4d87-b7d1-82c09e0c7363)] +interface nsIXmlRpcClientListener: nsISupports { + /** + * Called when XML-RPC call has succeeded. + * + * @param client The originating XML-RPC client. + * @param context The context passed in to the asyncCall method. + * @param result The result of the XML-RPC call. + */ + void onResult(in nsIXmlRpcClient client, in nsISupports ctxt, + in nsISupports result); + + /** + * Called when the XML-RPC server returned a Fault + * + * @param client The originating XML-RPC client. + * @param context The context passed in to the asyncCall method. + * @param fault The XML-RPC fault as returned by the server. + */ + void onFault(in nsIXmlRpcClient client, in nsISupports ctxt, + in nsIXmlRpcFault fault); + + /** + * Called when a transport or other error occurs. + * + * @param client The originating XML-RPC client. + * @param context The context passed in to the asyncCall method. + * @param status The status code of the error. + * @param errorMsg A human readable error message. + */ + void onError(in nsIXmlRpcClient client, in nsISupports ctxt, + in nsresult status, in wstring errorMsg); +}; + +// vim:sw=4:sr:sta:et:sts: diff --git a/mozilla/extensions/xml-rpc/makefile.win b/mozilla/extensions/xml-rpc/makefile.win new file mode 100644 index 00000000000..74dafb24ae0 --- /dev/null +++ b/mozilla/extensions/xml-rpc/makefile.win @@ -0,0 +1,27 @@ +# +# 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 and XPCOM Dictionary components. +# +# The Initial Developer of the Original Code is Digital Creations 2, Inc. +# Portions created by Digital Creations 2, Inc. are Copyright (C) 2000 Digital +# Creations 2, Inc. All Rights Reserved. +# +# Contributor(s): Martijn Pieters (original author) +# + +DEPTH =..\.. + +DIRS = \ + idl \ + src \ + $(NULL) + +include <$(DEPTH)\config\rules.mak> + diff --git a/mozilla/extensions/xml-rpc/src/MANIFEST_COMPONENTS b/mozilla/extensions/xml-rpc/src/MANIFEST_COMPONENTS new file mode 100644 index 00000000000..d9ec0a2e1fa --- /dev/null +++ b/mozilla/extensions/xml-rpc/src/MANIFEST_COMPONENTS @@ -0,0 +1,15 @@ +# 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 and XPCOM Dictionary components. +# +# The Initial Developer of the Original Code is Digital Creations 2, Inc. +# Portions created by Digital Creations 2, Inc. are Copyright (C) 2000 Digital +# Creations 2, Inc. All Rights Reserved. +# +# Contributor(s): Martijn Pieters (original author) +# + +nsDictionary.js +nsXmlRpcClient.js diff --git a/mozilla/extensions/xml-rpc/src/Makefile.in b/mozilla/extensions/xml-rpc/src/Makefile.in new file mode 100644 index 00000000000..f9a9a63d212 --- /dev/null +++ b/mozilla/extensions/xml-rpc/src/Makefile.in @@ -0,0 +1,33 @@ +# +# 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 and XPCOM Dictionary components. +# +# The Initial Developer of the Original Code is Digital Creations 2, Inc. +# Portions created by Digital Creations 2, Inc. are Copyright (C) 2000 Digital +# Creations 2, Inc. All Rights Reserved. +# +# Contributor(s): Martijn Pieters (original author) +# + +DEPTH = ../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +JSFILES = \ + $(srcdir)/nsDictionary.js \ + $(srcdir)/nsXmlRpcClient.js \ + +include $(topsrcdir)/config/rules.mk + +install:: + $(INSTALL) $(JSFILES) $(DIST)/bin/components diff --git a/mozilla/extensions/xml-rpc/src/makefile.win b/mozilla/extensions/xml-rpc/src/makefile.win new file mode 100644 index 00000000000..b73bbd143da --- /dev/null +++ b/mozilla/extensions/xml-rpc/src/makefile.win @@ -0,0 +1,29 @@ +# +# 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 and XPCOM Dictionary components. +# +# The Initial Developer of the Original Code is Digital Creations 2, Inc. +# Portions created by Digital Creations 2, Inc. are Copyright (C) 2000 Digital +# Creations 2, Inc. All Rights Reserved. +# +# Contributor(s): Martijn Pieters (original author) +# + +DEPTH =..\..\.. + +JSFILES = \ + .\nsDictionary.js \ + .\nsXmlRpcClient.js \ + $(NULL) + +include <$(DEPTH)\config\rules.mak> + +install:: + $(MAKE_INSTALL) $(JSFILES) $(DIST)\bin\components diff --git a/mozilla/extensions/xml-rpc/src/nsDictionary.js b/mozilla/extensions/xml-rpc/src/nsDictionary.js new file mode 100644 index 00000000000..374e52c8194 --- /dev/null +++ b/mozilla/extensions/xml-rpc/src/nsDictionary.js @@ -0,0 +1,125 @@ +/* + * 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 XPCOM Dictionary. + * + * The Initial Developer of the Original Code is Digital Creations 2, Inc. + * Portions created by Digital Creations 2, Inc. are Copyright (C) 2000 Digital + * Creations 2, Inc. All Rights Reserved. + * + * Contributor(s): Martijn Pieters (original author) + */ + +/* + * nsDictionary XPCOM component + * Version: $Revision: 1.1 $ + * + * $Id: nsDictionary.js,v 1.1 2000-05-05 06:06:34 mj%digicool.com Exp $ + */ + +/* + * Constants + */ +const DICTIONARY_PROGID = 'mozilla.dictionary.1'; +const DICTIONARY_CID = Components.ID('{1dd0cb45-aea3-4a52-8b29-01429a542863}'); +const DICTIONARY_IID = Components.interfaces.nsIDictionary; + +/* + * Class definitions + */ + +/* The nsDictionary class constructor. */ +function nsDictionary() { + this.hash = {}; +} + +/* the nsDictionary class def */ +nsDictionary.prototype= { + hasKey: function(key) { return this.hash.hasOwnProperty(key) }, + + getKeys: function(count) { + var asKeys = new Array(); + for (sKey in this.hash) asKeys.push(sKey); + count.value = asKeys.length; + return asKeys; + }, + + getValue: function(key) { + if (!this.hasKey(key)) + throw Components.Exception("Key doesn't exist"); + return this.hash[key]; + }, + + setValue: function(key, value) { this.hash[key] = value; }, + + deleteValue: function(key) { + if (!this.hasKey(key)) + throw Components.Exception("Key doesn't exist"); + var oOld = this.getValue(key); + delete this.hash[key]; + return oOld; + }, + + clear: function() { this.hash = {}; }, + + QueryInterface: function(iid) { + if (!iid.equals(Components.interfaces.nsISupports) && + !iid.equals(DICTIONARY_IID)) + throw Components.results.NS_ERROR_NO_INTERFACE; + return this; + } +}; + +/* + * Objects + */ + +/* nsDictionary Module (for XPCOM registration) */ +var nsDictionaryModule = { + registerSelf: function(compMgr, fileSpec, location, type) { + compMgr.registerComponentWithType(DICTIONARY_CID, + "nsDictionary JS component", DICTIONARY_PROGID, fileSpec, location, + true, true, type); + }, + + getClassObject: function(compMgr, cid, iid) { + if (!cid.equals(DICTIONARY_CID)) + throw Components.results.NS_ERROR_NO_INTERFACE; + + if (!iid.equals(Components.interfaces.nsIFactory)) + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + + return nsDictionaryFactory; + }, + + canUnload: function(compMgr) { return true; } +}; + +/* nsDictionary Class Factory */ +var nsDictionaryFactory = { + CreateInstance: function(outer, iid) { + if (outer != null) + throw Components.results.NS_ERROR_NO_AGGREGATION; + + if (!iid.equals(DICTIONARY_IID) && + !iid.equals(Components.interfaces.nsISupports)) + throw Components.results.NS_ERROR_INVALID_ARG; + + return new nsDictionary(); + } +} + +/* + * Functions + */ + +/* module initialisation */ +function NSGetModule(comMgr, fileSpec) { return nsDictionaryModule; } + +// vim:sw=4:sr:sta:et:sts: diff --git a/mozilla/extensions/xml-rpc/src/nsXmlRpcClient.js b/mozilla/extensions/xml-rpc/src/nsXmlRpcClient.js new file mode 100644 index 00000000000..852d5cba47e --- /dev/null +++ b/mozilla/extensions/xml-rpc/src/nsXmlRpcClient.js @@ -0,0 +1,1433 @@ +/* + * 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 Digital Creations 2, Inc. are Copyright (C) 2000 Digital + * Creations 2, Inc. All Rights Reserved. + * + * Contributor(s): Martijn Pieters (original author) + */ + +/* + * nsXmlRpcClient XPCOM component + * Version: $Revision: 1.1 $ + * + * $Id: nsXmlRpcClient.js,v 1.1 2000-05-05 06:06:34 mj%digicool.com Exp $ + */ + +/* + * Constants + */ +const XMLRPCCLIENT_PROGID = 'mozilla.xml-rpc.client.1'; +const XMLRPCCLIENT_CID = + Components.ID('{37127241-1e6e-46aa-ba87-601d41bb47df}'); +const XMLRPCCLIENT_IID = Components.interfaces.nsIXmlRpcClient; + +const XMLRPCFAULT_PROGID = 'mozilla.xml-rpc.fault.1'; +const XMLRPCFAULT_CID = + Components.ID('{691cb864-0a7e-448c-98ee-4a7f359cf145}'); +const XMLRPCFAULT_IID = Components.interfaces.nsIXmlRpcFault; + +const DEBUG = false; +const DEBUGPARSE = false; + +/* + * 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 ''; + } +}; + +/* The nsXmlRpcClient class constructor. */ +function nsXmlRpcClient() {} + +/* the nsXmlRpcClient class def */ +nsXmlRpcClient.prototype = { + _serverUrl: null, + + init: function(serverURL) { + oURL = createInstance('component://netscape/network/standard-url', + 'nsIURL'); + oURL.spec = serverURL; + + // Make sure it is a complete spec + // Note that we don't care what the scheme is otherwise. + // Should we care? POST works only on http and https.. + if (!oURL.scheme) oURL.scheme = 'http'; + if (oURL.scheme != 'http') + throw Components.Exceptions('Only HTTP is supported'); + + this._serverUrl = oURL; + }, + + get serverUrl() { return this._serverUrl; }, + + // BROKEN. Bug 37913 + call: function(methodName) { + debug('call'); + // 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 initilized'); + + this._inProgress = true; + + // Clear state. + this._status = null; + this._errorMsg = null; + + var methodArgs = [].concat(arguments); + methodArgs.shift(); + debug('Arguments: ' + methodArgs); + + // Generate request body + var xmlWriter = new XMLWriter(); + this._generateRequestBody(xmlWriter, methodName, methodArgs); + + var requestBody = xmlWriter.data; + + debug('Request: ' + requestBody); + + var channel = this._getChannel(requestBody); + + debug('Do the deed.'); + + var input = channel.openInputStream(); + input = toScriptableStream(input); + + var now = new Date() + // This is broken! See bug 11859 + // yes, if we use asyncCall on ourselves, we still don't + // work. Valeski calls this an event pump blocking problem. + while (!input.available()) { // Wait for data + if (new Date() - now > 1 * 60 * 1000) + throw Components.Exception('Connection timed out'); + } + + this._reponseStatus = channel.ResponseStatus; + this._responseString = channel.ResponseString; + + // Check for a 200 response. + if (channel.ResponseStatus != 200) { + this._status = Components.results.NS_ERROR_FAILURE; + this._errorMsg = 'Server returned unexpected status ' + + channel.ResponseStatus; + this._inProgress = false; + throw Components.Exception('Server returned unexpected status ' + + channel.ResponseStatus); + } + + // check content type + if (channel.contentType != 'text/xml') { + this._status = Components.results.NS_ERROR_FAILURE; + this._errorMsg = 'Server returned unexpected content-type ' + + channel.contentType; + this._inProgress = false; + throw Components.Exception('Server returned unexpected ' + + 'content-type ' + channel.contentType); + } + + debug('Viable response. Let\'s parse!'); + debug('Content length = ' + channel.contentLength); + + try { + this._parseResponse(toScriptableStream(inStr), + channel.contentLength); + debug('Parse finished'); + debug('Fault? ' + this._fault); + debug('Result? ' + this._result); + if (this._fault) + throw Components.Exception('XML-RPC Fault', null, null, + this._fault); + else + return this._result; + } catch(ex) { + this._status = ex.result; + this._errorMsg = ex.message; + ctxt.listener.onError(this, ctxt.context, ex.result, ex.message); + } finally { + this._inProgress = false; + } + }, + + // Internal copy of the status, so's we can throw it to the syncnronous + // caller. + _status: null, + _errorMsg: null, + + asyncCall: function(listener, context, methodName) { + 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 initilized'); + + this._inProgress = true; + + // Clear state. + this._status = null; + this._errorMsg = null; + + var internalContext = { + listener: listener, + context: context, + seenStart: false, + } + + var methodArgs = [].concat(arguments); + methodArgs.splice(0, 3); + debug('Arguments: ' + methodArgs); + + // Generate request body + var xmlWriter = new XMLWriter(); + this._generateRequestBody(xmlWriter, methodName, methodArgs); + + var requestBody = xmlWriter.data; + + debug('Request: ' + requestBody); + + var chann = this._getChannel(requestBody); + + // And...... call! + chann.asyncRead(this, internalContext); + }, + + // Return a HTTP channel ready for POSTing. + _getChannel: function(request) { + // Set up channel. + var ioService = getService('component://netscape/network/io-service', + 'nsIIOService'); + var atomService = getService('component://netscape/atom-service', + 'nsIAtomService'); + + var chann = ioService.newChannelFromURI(this._serverUrl) + .QueryInterface(Components.interfaces.nsIHTTPChannel); + + // Set the request method. + chann.SetRequestMethod(atomService.getAtom('POST')); + + // Create a stream out of the request and attach it to the channel + // Note: pending bug #37773, an extra \r\n needs to be added. + var handler = ioService.getProtocolHandler(this._serverUrl.scheme) + .QueryInterface(Components.interfaces.nsIHTTPProtocolHandler); + var postStream = handler.NewPostDataStream(false, '\r\n' + request, + handler.ENCODE_NORMAL); + chann.UploadStream = postStream; + + // Set the request headers + chann.SetRequestHeader(atomService.getAtom('content-type'), 'text/xml'); + chann.SetRequestHeader(atomService.getAtom('content-length'), + request.length); + + return chann; + }, + + // Flag indicating wether or not we are calling the server. + _inProgress: false, + get inProgress() { return this._inProgress; }, + + // nsIStreamListener interface, so's we know about the pending request. + onStartRequest: function(channel, ctxt) { + debug('Start Request') + }, // Do exactly nada. + + // End of the request + onStopRequest: function(channel, ctxt, status, errorMsg) { + debug('Stop Request'); + if (!this._inProgress) return; // No longer interested. + + this._inProgress = false; + this._parser = null; + + if (status != Components.results.NS_OK) { + this._status = status; + this._errorMsg = errorMsg; + try { ctxt.listener.onError(this, ctxt.context, status, errorMsg); } + catch (ex) {} + return; + } + + // All done. + debug('Parse finished'); + if (this._fault) { + try { + this._fault = createInstance(XMLRPCFAULT_PROGID, + 'nsIXmlRpcFault'); + this._fault.init(this._result.getValue('faultCode').data, + this._result.getValue('faultString').data); + this._result = null; + } catch(e) { + this._fault = null; + this._result = null; + throw Components.Exception('Could not parse response'); + try { + ctxt.listener.onError(this, ctxt.context, + Components.results.NS_ERROR_FAIL, + 'Server returned invalid Fault'); + } + catch(ex) {} + } + debug('Fault: ' + this._fault); + try { ctxt.listener.onFault(this, ctxt.context, this._fault); } + catch(ex) {} + } else { + debug('Result: ' + this._result); + try { + ctxt.listener.onResult(this, ctxt.context, this._result); + } catch (ex) {} + } + }, + + _parser: null, + + // Houston, we have data. Process on first call, don't look back. + onDataAvailable: function(channel, ctxt, inStr, sourceOffset, count) { + debug('Data available (' + sourceOffset + ', ' + count + ')'); + if (!this._inProgress) return; // No longer interested. + + if (!ctxt.seenStart) { + // First time round + ctxt.seenStart = true; + + // Store request status and message. + channel = channel + .QueryInterface(Components.interfaces.nsIHTTPChannel); + this._reponseStatus = channel.ResponseStatus; + this._responseString = channel.ResponseString; + + // Check for a 200 response. + if (channel.ResponseStatus != 200) { + this._status = Components.results.NS_ERROR_FAILURE; + this._errorMsg = 'Server returned unexpected status ' + + channel.ResponseStatus; + this._inProgress = false; + try { + ctxt.listener.onError(this, ctxt.context, + Components.results.NS_ERROR_FAILURE, + 'Server returned unexpected status ' + + channel.ResponseStatus); + } catch (ex) {} + return; + } + + // check content type + if (channel.contentType != 'text/xml') { + this._status = Components.results.NS_ERROR_FAILURE; + this._errorMsg = 'Server returned unexpected content-type ' + + channel.contentType; + this._inProgress = false; + try { + ctxt.listener.onError(this, ctxt.context, + Components.results.NS_ERROR_FAILURE, + 'Server returned unexpected content-type ' + + channel.contentType); + } catch (ex) {} + return; + } + + debug('Viable response. Let\'s parse!'); + debug('Content length = ' + channel.contentLength); + + this._parser = new SimpleXMLParser(toScriptableStream(inStr), + channel.contentLength); + this._parser.setDocumentHandler(this); + + // Make sure state is clean + _valueStack = []; + _currValue = null; + _cdata = null; + } + + debug('Cranking up the parser, window = ' + count); + try { + this._parser.parse(count); + } catch(ex) { + this._status = ex.result; + this._errorMsg = ex.message; + ctxt.listener.onError(this, ctxt.context, ex.result, ex.message); + this._inProgress = false; + this._parser = null; + } + + }, + + _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) { + const SUPPORTSID = 'component://netscape/supports-'; + switch(type) { + case this.INT: + return createInstance(SUPPORTSID + 'PRInt32', + 'nsISupportsPRInt32'); + + case this.BOOLEAN: + return createInstance(SUPPORTSID + 'PRBool', + 'nsISupportsPRBool'); + + case this.STRING: + return createInstance(SUPPORTSID + 'string', + 'nsISupportsString'); + + case this.DOUBLE: + return createInstance(SUPPORTSID + 'double', + 'nsISupportsDouble'); + + case this.DATETIME: + return createInstance(SUPPORTSID + 'PRTime', + 'nsISupportsPRTime'); + + case this.ARRAY: + return createInstance(SUPPORTSID + 'array', 'nsISupportsArray'); + + case this.STRUCT: + return createInstance('mozilla.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.nsIXmlRpcClientListener) && + !iid.equals(Components.interfaces.nsIStreamObserver) && + !iid.equals(Components.interfaces.nsIStreamListener)) + throw Components.results.NS_ERROR_NO_INTERFACE; + return this; + }, + + /* 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 in methodArgs) { + 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 'String': + 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'); + keys = obj.getKeys({}); + for (var i in keys) { + writer.startElement('member'); + writer.startElement('name'); + writer.write(keys[i]); + writer.endElement('name'); + this._generateArgumentBody(writer, obj.getValue(keys[i])); + 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.nsISupportsString); + return 'String'; + } 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'; + }, + + /* Parse the XML-RPC response */ + // Only on Sync calls. + _parseResponse: function(stream, length) { + debug('Creating parser ... '); + var parser = new SimpleXMLParser(stream, length); + parser.setDocumentHandler(this); + + // Make sure state is clean + _valueStack = []; + _currValue = null; + _cdata = null; + + debug('Cranking up the parser'); + parser.parse(length); + + if (this._fault) { + try { + this._fault = createInstance(XMLRPCFAULT_PROGID, + 'nsIXmlRpcFault'); + this._fault.init(this._result.getValue('faultCode').data, + this._result.getValue('faultString').data); + this._result = null; + } catch(e) { + this._fault = null; + this._result = null; + throw Components.Exception('Could not parse response'); + } + } + }, + + // Response parsing state + _valueStack: [], + _currValue: null, + _cdata: null, + + /* SAX documentHandler interface (well, sorta) */ + characters: function(chars) { + if (DEBUGPARSE) debug('character data: ' + chars); + if (this._cdata == null) return; + this._cdata += chars; + }, + + startElement: function(name) { + if (DEBUGPARSE) debug('Start element ' + name); + switch (name) { + case 'fault': + this._fault = true; + break; + + case 'value': + var val = new Value(); + this._valueStack.push(val); + this._currValue = val; + this._cdata = ''; + break; + + case 'name': + this._cdata = ''; + break; + + case 'i4': + case 'int': + this._currValue.type = this.INT; + break; + + case 'boolean': + this._currValue.type = this.BOOLEAN; + break; + + case 'double': + this._currValue.type = this.DOUBLE; + break; + + case 'dateTime.iso8601': + this._currValue.type = this.DATETIME; + break; + + case 'base64': + this._currValue.type = this.BASE64; + break; + + case 'struct': + this._currValue.type = this.STRUCT; + break; + + case 'array': + this._currValue.type = this.ARRAY; + break; + } + }, + + endElement: function(name) { + if (DEBUGPARSE) debug('End element ' + name); + switch (name) { + case 'value': + // take cdata and put it in this value; + if (this._currValue.type != this.ARRAY && + this._currValue.type != this.STRUCT) { + this._currValue.value = this._cdata; + this._cdata = null; + } + + // Find out if this is the end value + // Note that we treat a struct differently, see 'member' + var depth = this._valueStack.length; + if (depth < 2 || + this._valueStack[depth - 2].type != this.STRUCT) { + var val = this._currValue; + this._valueStack.pop(); + + if (depth < 2) { + if (DEBUG) debug('Found result'); + // This is the top level object + this._result = val.value; + this._currValue = null; + } else { + // This is an array element. Add it. + this._currValue = + this._valueStack[this._valueStack.length - 1]; + this._currValue.appendValue(val.value); + } + } + break; + + case 'member': + var val = this._currValue; + this._valueStack.pop(); + this._currValue = this._valueStack[this._valueStack.length - 1]; + this._currValue.appendValue(val.value); + break; + + case 'name': + this._currValue.name = this._cdata; + this._cdata = null; + break; + } + } +}; + +/* The XMLWriter class constructor */ +function XMLWriter() { + // We assume for now that all data is already in ISO-8859-1. + this.data = '\n'; +} + +/* The XMLWriter class def */ +XMLWriter.prototype = { + data: '', + + startElement: function(element) { + this.data += '<' + element + '>'; + }, + + endElement: function(element) { + this.data += ''; + }, + + write: function(text) { + for (var i in text) { + var c = text.charAt(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. + const entityTrans = new Function('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 = 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; + } + } +}; + +/* The SimpleXMLParser class constructor + * This parser is specific to the XML-RPC format! + * It assumes tags without arguments, in lowercase. + */ +function SimpleXMLParser(instream, contentLength) { + this._stream = new PushbackInputStream(instream); + this._maxlength = contentLength; +} + +/* The SimpleXMLParser class def */ +SimpleXMLParser.prototype = { + _stream: null, + _docHandler: null, + _bufferSize: 256, + _parsed: 0, + _maxlength: 0, + _window: 0, // When async on big documents, release after windowsize. + + setDocumentHandler: function(handler) { this._docHandler = handler; }, + + parse: function(windowsize) { + this._window += windowsize; + + this._start(); + }, + + // Guard maximum length + _read: function(length) { + length = Math.min(this._available(), length); + if (!length) return ''; + var read = this._stream.read(length); + this._parsed += read.length; + return read; + }, + _unread: function(data) { + this._stream.unread(data); + this._parsed -= data.length; + }, + _available: function() { + return Math.min(this._stream.available(), this._maxAvailable()); + }, + _maxAvailable: function() { return this._maxlength - this._parsed; }, + + // read length characters from stream, block until we get them. + _blockingRead: function(length) { + length = Math.min(length, this._maxAvailable()); + if (!length) return ''; + var read = ''; + while (read.length < length) read += this._read(length - read.length); + return read; + }, + + // read until the the 'findChar' character appears in the stream. + // We read no more than _bufferSize characters, and return what we have + // found so far, but no more than up to 'findChar' if found. + _readUntil: function(findChar) { + var read = this._blockingRead(this._bufferSize); + var pos = read.indexOf(findChar.charAt(0)); + if (pos > -1) { + this._unread(read.slice(pos + 1)); + return read.slice(0, pos + 1); + } + return read; + }, + + // Skip stream until string end is found. + _skipUntil: function(end) { + var read = ''; + while (this._maxAvailable()) { + read += this._readUntil(end.charAt(0)) + + this._blockingRead(end.length - 1); + var pos = read.indexOf(end); + if (pos > -1) { + this._unread(read.slice(pos + end.length)); + return; + } + read = read.slice(-(end.length)); // make sure don't miss our man. + } + return; + }, + + _buff: '', + // keep track of whitespce, so's we can discard it. + _killLeadingWS: false, + _trailingWS: '', + + _start: function() { + // parse until exhausted. Note that we only look at a window + // of max. this._bufferSize. Also, parsing of comments, PI's and + // CDATA isn't as solid as it could be. *shrug*, XML-RPC responses + // are 99.99% of the time generated anyway. + // We don't check well-formedness either. Errors in tags will + // be caught at the doc handler. + ParseLoop: while (this._maxAvailable() || this._buff) { + // Check for window size. We stop parsing until more comes + // available (only in async parsing). + if (this._window < this._maxlength && + this._parsed >= this._window) + return; + + this._buff += this._read(this._bufferSize - this._buff.length); + this._buff = this._buff.replace('\r\n', '\n'); + this._buff = this._buff.replace('\r', '\n'); + + var startTag = this._buff.indexOf('<'); + if (startTag > -1) { + if (startTag > 0) { // We have character data. + var chars = this._buff.slice(0, startTag); + chars = chars.replace(/[ \t\n]*$/, ''); + if (chars && this._killLeadingWS) + chars = chars.replace(/^[ \t\n]*/, ''); + if (chars) { + // Any whitespace previously marked as trailing is in + // fact in the middle. Prepend. + chars = this._trailingWS + chars; + this._docHandler.characters(chars); + } + this._buff = this._buff.slice(startTag); + this._trailingWS = ''; + this._killLeadingWS = false; + } + + // Check for a PI + if (this._buff.charAt(1) == '?') { + var endTag = this._buff.indexOf('?>'); + if (endTag > -1) this._buff = this._buff.slice(endTag + 2); + else { + // Make sure we don't miss '?' at the end of the buffer + this._unread(this._buff.slice(-1)); + this._buff = ''; + this._skipUntil('?>'); + } + this._killLeadingWS = true; + continue; + } + + // Check for a comment + if (this._buff.slice(0, 4) == ''); + if (endTag > -1) this._buff = this._buff.slice(endTag + 3); + else { + // Make sure we don't miss '--' at the end of the buffer + this._unread(this._buff.slice(-2)); + this._buff = ''; + this._skipUntil('-->'); + } + this._killLeadingWS = true; + continue; + } + + // Check for CDATA + // We only check the first four characters. Anything longer and + // we'd miss it and it would be recognized as a corrupt element + // Anything shorter will be missed by the element scanner as + // well. Next loop we'll have more characters to do a better + // match. + if (this._buff.slice(0, 4) == ''); + if (endTag > -1) { + this._buff = this._buff.slice(endTag + 3); + this._docHandler.characters(this._buff.slice(9, + endTag)); + this._killLeadingWS = true; + continue; + } + + // end not in stream. Hrmph + this._docHandler.characters(this._buff.slice(9)); + this._buff = ''; + while(this._maxAvailable()) { + this._buff += this._readUntil(']') + + this._blockingRead(2); + // Find end. + var pos = this._buff.indexOf(']]>'); + // Found. + if (pos > -1) { + this._docHandler.characters(this._buff.slice(0, + pos)); + this._buff = this._buff.slice(pos + 3); + this._killLeadingWS = true; + continue ParseLoop; + } + // Not yet found. Last 2 chars could be part of end. + this._docHandler.characters(this._buff.slice(0, -2)); + this._buff = this._buff.slice(-2); + } + + if (this._buff) // Uhoh. No ]]> found before EOF. + throw Components.Exception('Error parsing response'); + + continue; + } + + // Check for a DOCTYPE decl. + if (this._buff.slice(0, 4) == ' -1) { + this._unread(this._buff.slice(startBrace + 1)); + this._buff = ''; + this._skipUntil(']'); + this._skipUntil('>'); + this._killLeadingWS = true; + continue; + } + + var endTag = this._buff.indexOf('>'); + if (endTag > -1) { + this._buff = this._buff.slice(endTag + 1); + this._killLeadingWS = true; + continue; + } + + this._buff = ''; + while(this._available()) { + this._buff = this._readUntil('>'); + + var startBrace = this._buff.indexOf('['); + if (startBrace > -1) { + this._unread(this._buff.slice(startBrace + 1)); + this._buff = ''; + this._skipUntil(']'); + this._skipUntil('>'); + this._killLeadingWS = true; + continue ParseLoop; + } + + var endTag = this._buff.indexOf('>'); + if (endTag > -1) { + this._buff = this._buff.slice(pos + 1); + this._killLeadingWS = true; + continue; + } + } + + if (this._buff) + throw Components.Exception('Error parsing response'); + + continue; + } + + var endTag = this._buff.indexOf('>'); + if (endTag > -1) { + var tag = this._buff.slice(1, endTag); + this._buff = this._buff.slice(endTag + 1); + tag = tag.replace(/[ \t\n]+.*?(\/?)$/, '$1'); + + // XML-RPC tags are pretty simple. + if (/[^a-zA-Z0-9.\/]/.test(tag)) + throw Components.Exception('Error parsing response'); + + // Determine start and/or end tag. + if (tag.charAt(tag.length - 1) == '/') { + this._docHandler.startElement(tag.slice(0, -1)); + this._docHandler.endElement(tag.slice(0, -1)); + } else if (tag.charAt(0) == '/') { + this._docHandler.endElement(tag.slice(1)); + } else { + this._docHandler.startElement(tag); + } + this._killLeadingWS = true; + } else { + // No end tag. Check for window size to avoid an endless + // loop here.. hackish, I know, but if we get here this is + // not a XML-RPC request.. + if (this._buff.length >= this._bufferSize) + throw Components.Exception('Error parsing response'); + // If we get here and all what is to be read has + // been readinto the buffer, we have an incomplete stream. + if (!this._maxAvailable()) + throw Components.Exception('Error parsing response'); + } + } else { + if (this._killLeadingWS) { + this._buff = this._buff.replace(/^[ \t\n]*/, ''); + if (this._buff) this._killLeadingWS = false; + } else { + // prepend supposed trailing whitespace to the front. + this._buff = this._trailingWS + this._buff; + this._trailingWS = ''; + } + + // store trailing whitespace, and only hand it over + // the next time round. Unless we hit a tag, then we kill it + if (this._buff) { + this._trailingWS = this._buff.match(/[ \t\n]*$/); + this._buff = this._buff.replace(/[ \t\n]*$/, ''); + } + + if (this._buff) this._docHandler.characters(this._buff); + + this._buff = ''; + } + } + } +}; + + +/* The PushbackInputStream class constructor */ +function PushbackInputStream(stream) { + this._stream = stream; +} + +/* The PushbackInputStream class def */ +PushbackInputStream.prototype = { + _stream: null, + _read_characters: '', + + available: function() { + return this._read_characters.length + this._stream.available(); + }, + + read: function(length) { + if (this._read_characters.length >= length) { + var read = this._read_characters.slice(0, length); + this._read_characters = this._read_characters.slice(length); + return read; + } else { + var read = this._read_characters; + this._read_characters = ''; + return read + this._stream.read(length - read.length); + } + }, + + unread: function(chars) { + this._read_characters = chars + this._read_characters; + } +}; + +/* + * Objects + */ + +/* nsXmlRpcClient Module (for XPCOM registration) */ +var nsXmlRpcClientModule = { + registerSelf: function(compMgr, fileSpec, location, type) { + compMgr.registerComponentWithType(XMLRPCCLIENT_CID, + 'XML-RPC Client JS component', XMLRPCCLIENT_PROGID, fileSpec, + location, true, true, type); + compMgr.registerComponentWithType(XMLRPCFAULT_CID, + 'XML-RPC Fault JS component', XMLRPCFAULT_PROGID, fileSpec, + location, true, true, 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 ProgID, with given interface */ +function createInstance(progId, intf) { + return Components.classes[progId] + .createInstance(Components.interfaces[intf]); +} + +/* Get a pointer to a service indicated by the ProgID, with given interface */ +function getService(progId, intf) { + return Components.classes[progId].getService(Components.interfaces[intf]); +} + +/* Convert an inputstream to a scriptable inputstream */ +function toScriptableStream(input) { + var SIStream = Components.Constructor( + 'component://netscape/scriptableinputstream', + '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 = date.getUTCMonth() + 1; + datetime += (month < 10 ? '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 devidable by 3!! +function streamToBase64(stream, writer) { + while (stream.available()) { + var data = []; + while (data.length < BASE64CHUNK && stream.available()) { + 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) { + result = ''; + length = data.length; + // Convert every three bytes to 4 ascii characters. + while (length > 2) { + result += toBase64Table.charAt(data[0] >> 2); + result += toBase64Table.charAt(((data[0] & 0x03) << 4) + + (data[1] >> 4)); + result += toBase64Table.charAt(((data[1] & 0x0f) << 2) + + (data[2] >> 6)); + result += toBase64Table.charAt(data[2] & 0x3f); + + data = data.slice(3); + length -= 3; + } + + // Convert the remaining 1 or 2 bytes, pad out to 4 characters. + if (length > 0) { + result += toBase64Table.charAt(data[0] >> 2); + if (length > 1) { + result += toBase64Table.charAt(((data[0] & 0x03) << 4) + (data[1] >> 4)); + result += toBase64Table.charAt((data[1] & 0x0f) << 2); + result += base64Pad; + } else { + result += toBase64Table.charAt((data[0] & 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, bt yet to be appended + + // Convert one by one. + for (var i in data) { + var c = toBinaryTable[data.charCodeAt(i) & 0x7f]; + var padding = (data.charAt(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: