diff --git a/libsecondlife-cs/NetworkManager.cs b/libsecondlife-cs/NetworkManager.cs index eb09bfbb..8782e8e5 100644 --- a/libsecondlife-cs/NetworkManager.cs +++ b/libsecondlife-cs/NetworkManager.cs @@ -26,6 +26,7 @@ using System; using System.Timers; +using System.Collections; using System.Collections.Generic; using System.Net; using System.Net.Sockets; @@ -632,7 +633,7 @@ namespace libsecondlife /// The complete dictionary of all the login values returned by the /// RPC login server, converted to native data types wherever possible /// - public Dictionary LoginValues; + public Dictionary LoginValues = new Dictionary(); /// /// Shows whether the network layer is logged in to the grid or not /// @@ -651,9 +652,9 @@ namespace libsecondlife /// public DisconnectCallback OnDisconnected; - private Dictionary> Callbacks; private SecondLife Client; - private List Simulators; + private Dictionary> Callbacks = new Dictionary>(); + private List Simulators = new List(); private System.Timers.Timer DisconnectTimer; private bool connected; @@ -667,10 +668,7 @@ namespace libsecondlife public NetworkManager(SecondLife client) { Client = client; - Simulators = new List(); - Callbacks = new Dictionary>(); CurrentSim = null; - LoginValues = null; // Register the internal callbacks RegisterCallback(PacketType.RegionHandshake, new PacketCallback(RegionHandshakeHandler)); @@ -951,11 +949,30 @@ namespace libsecondlife /// public bool Login(Dictionary loginParams, string url) { + // Rebuild the Dictionary<> in to a Hashtable for compatibility with XmlRpcCS + Hashtable loginValues = new Hashtable(loginParams.Count); + foreach (KeyValuePair kvp in loginParams) + { + if (kvp.Value is IList) + { + IList list = ((IList)kvp.Value); + ArrayList array = new ArrayList(list.Count); + foreach (object obj in list) + { + array.Add(obj); + } + } + else + { + loginValues[kvp.Key] = kvp.Value; + } + } + XmlRpcResponse result; XmlRpcRequest xmlrpc = new XmlRpcRequest(); xmlrpc.MethodName = "login_to_simulator"; xmlrpc.Params.Clear(); - xmlrpc.Params.Add(loginParams); + xmlrpc.Params.Add(loginValues); try { @@ -964,7 +981,7 @@ namespace libsecondlife catch (Exception e) { LoginError = "XML-RPC Error: " + e.Message; - LoginValues = null; + LoginValues.Clear(); return false; } @@ -972,11 +989,15 @@ namespace libsecondlife { Client.Log("Fault " + result.FaultCode + ": " + result.FaultString, Helpers.LogLevel.Error); LoginError = "XML-RPC Fault: " + result.FaultCode + ": " + result.FaultString; - LoginValues = null; + LoginValues.Clear(); return false; } - LoginValues = (Dictionary)result.Value; + Hashtable values = (Hashtable)result.Value; + foreach (DictionaryEntry entry in values) + { + LoginValues[(string)entry.Key] = entry.Value; + } if ((string)LoginValues["login"] == "indeterminate") { diff --git a/libsecondlife-cs/ParticleSystem.cs b/libsecondlife-cs/ParticleSystem.cs index 88d8065e..e6653a7b 100644 --- a/libsecondlife-cs/ParticleSystem.cs +++ b/libsecondlife-cs/ParticleSystem.cs @@ -7,6 +7,7 @@ namespace libsecondlife /// /// /// + [Serializable] public class ParticleSystem { /// diff --git a/libsecondlife-cs/Prims.cs b/libsecondlife-cs/Prims.cs index 550cf6da..1d25ac43 100644 --- a/libsecondlife-cs/Prims.cs +++ b/libsecondlife-cs/Prims.cs @@ -95,7 +95,7 @@ namespace libsecondlife /// /// Sweet delicious prims. /// - [Serializable, XmlRoot("prim")] + [Serializable] public class PrimObject { /// @@ -161,19 +161,19 @@ namespace libsecondlife /// [XmlElement("position")] public LLVector3 Position = LLVector3.Zero; /// - [XmlElement("scale")] public LLVector3 Scale = LLVector3.Zero; + /*[XmlElement("scale")]*/ public LLVector3 Scale = LLVector3.Zero; /// - [XmlElement("rotation")] public LLQuaternion Rotation = LLQuaternion.Identity; + /*[XmlElement("rotation")]*/ public LLQuaternion Rotation = LLQuaternion.Identity; /// - [XmlElement("textures")] public TextureEntry Textures = new TextureEntry(); + /*[XmlElement("textures")]*/ public TextureEntry Textures = new TextureEntry(); /// - [XmlElement("textureanimation")] public TextureAnimation TextureAnim = new TextureAnimation(); + /*[XmlElement("textureanimation")]*/ public TextureAnimation TextureAnim = new TextureAnimation(); /// - [XmlElement("flexible")] public PrimFlexibleData Flexible = new PrimFlexibleData(); + /*[XmlElement("flexible")]*/ public PrimFlexibleData Flexible = new PrimFlexibleData(); /// - [XmlElement("light")] public PrimLightData Light = new PrimLightData(); + /*[XmlElement("light")]*/ public PrimLightData Light = new PrimLightData(); /// - [XmlElement("particles")] public ParticleSystem ParticleSys = new ParticleSystem(); + /*[XmlElement("particles")]*/ public ParticleSystem ParticleSys = new ParticleSystem(); /// /// Default constructor, zeroes out or sets default prim parameters @@ -487,6 +487,7 @@ namespace libsecondlife /// /// OMG Flexi /// + [Serializable] public class PrimFlexibleData { /// @@ -558,6 +559,7 @@ namespace libsecondlife /// /// Information on the light property associated with a prim /// + [Serializable] public class PrimLightData { /// diff --git a/libsecondlife-cs/SerializableDictionary.cs b/libsecondlife-cs/SerializableDictionary.cs index 73f6a502..b0613814 100644 --- a/libsecondlife-cs/SerializableDictionary.cs +++ b/libsecondlife-cs/SerializableDictionary.cs @@ -35,7 +35,7 @@ namespace libsecondlife /// /// Key type, used as indices to values /// Value type - [XmlRoot("dictionary")] + [Serializable] public class SerializableDictionary : Dictionary, IXmlSerializable { #region IXmlSerializable Members diff --git a/libsecondlife-cs/Textures.cs b/libsecondlife-cs/Textures.cs index fbab6a91..4354ff11 100644 --- a/libsecondlife-cs/Textures.cs +++ b/libsecondlife-cs/Textures.cs @@ -139,6 +139,7 @@ namespace libsecondlife /// you have a TextureEntry with a default texture uuid of X, and face 72 /// has a texture UUID of Y, every face would be textured with X except for /// face 72 that uses Y. + [Serializable] public class TextureEntry { /// @@ -620,7 +621,7 @@ namespace libsecondlife /// A single textured face. Don't instantiate this class yourself, use the /// methods in TextureEntry /// - [XmlRoot("face")] + [Serializable] public class TextureEntryFace { [XmlAttribute("rgba")] private uint rgba; @@ -829,6 +830,7 @@ namespace libsecondlife /// /// Controls the texture animation of a particular prim /// + [Serializable] public class TextureAnimation { /// diff --git a/libsecondlife-cs/Types.cs b/libsecondlife-cs/Types.cs index 9e9f14af..4fdca8f4 100644 --- a/libsecondlife-cs/Types.cs +++ b/libsecondlife-cs/Types.cs @@ -34,7 +34,7 @@ namespace libsecondlife /// A 128-bit Universally Unique Identifier, used throughout the Second /// Life networking protocol /// - [XmlRoot("uuid")] + [Serializable] public class LLUUID : IXmlSerializable { /// The 16 bytes that make up the UUID @@ -306,7 +306,7 @@ namespace libsecondlife /// /// A three-dimensional vector with floating-point values /// - [XmlRoot("vector3")] + [Serializable] public class LLVector3 { /// X value @@ -500,7 +500,7 @@ namespace libsecondlife /// /// A double-precision three-dimensional vector /// - [XmlRoot("vector3d")] + [Serializable] public class LLVector3d { /// X value @@ -599,7 +599,7 @@ namespace libsecondlife /// /// A four-dimensional vector /// - [XmlRoot("vector4")] + [Serializable] public class LLVector4 { /// @@ -691,7 +691,7 @@ namespace libsecondlife /// /// A quaternion, used for rotations /// - [XmlRoot("quaternion")] + [Serializable] public class LLQuaternion { /// X value diff --git a/libsecondlife-cs/XmlRpcCS/Logger.cs b/libsecondlife-cs/XmlRpcCS/Logger.cs index cb4b157f..ad3e5270 100644 --- a/libsecondlife-cs/XmlRpcCS/Logger.cs +++ b/libsecondlife-cs/XmlRpcCS/Logger.cs @@ -1,46 +1,46 @@ namespace Nwc.XmlRpc { - using System; + using System; - /// Define levels of logging. This duplicates - /// similar enumerations in System.Diagnostics.EventLogEntryType. The - /// duplication was merited because .NET Compact Framework lacked the EventLogEntryType enum. - public enum LogLevel - { - /// Information level, log entry for informational reasons only. - Information, - /// Warning level, indicates a possible problem. - Warning, - /// Error level, implies a significant problem. - Error - } - - /// - ///Logging singleton with swappable output delegate. - /// - /// - ///This singleton provides a centralized log. The actual WriteEntry calls are passed - ///off to a delegate however. Having a delegate do the actual logginh allows you to - ///implement different logging mechanism and have them take effect throughout the system. - /// - public class Logger - { - ///Delegate definition for logging. - ///The message String to log. - ///The LogLevel of your message. - public delegate void LoggerDelegate(String message, LogLevel level); - ///The LoggerDelegate that will recieve WriteEntry requests. - static public LoggerDelegate Delegate = null; + /// Define levels of logging. This duplicates + /// similar enumerations in System.Diagnostics.EventLogEntryType. The + /// duplication was merited because .NET Compact Framework lacked the EventLogEntryType enum. + public enum LogLevel + { + /// Information level, log entry for informational reasons only. + Information, + /// Warning level, indicates a possible problem. + Warning, + /// Error level, implies a significant problem. + Error + } /// - ///Method logging events are sent to. + ///Logging singleton with swappable output delegate. /// - ///The message String to log. - ///The LogLevel of your message. - static public void WriteEntry(String message, LogLevel level) - { - if (Delegate != null) - Delegate(message, level); - } - } + /// + ///This singleton provides a centralized log. The actual WriteEntry calls are passed + ///off to a delegate however. Having a delegate do the actual logginh allows you to + ///implement different logging mechanism and have them take effect throughout the system. + /// + public class Logger + { + ///Delegate definition for logging. + ///The message String to log. + ///The LogLevel of your message. + public delegate void LoggerDelegate(String message, LogLevel level); + ///The LoggerDelegate that will recieve WriteEntry requests. + static public LoggerDelegate Delegate = null; + + /// + ///Method logging events are sent to. + /// + ///The message String to log. + ///The LogLevel of your message. + static public void WriteEntry(String message, LogLevel level) + { + if (Delegate != null) + Delegate(message, level); + } + } } diff --git a/libsecondlife-cs/XmlRpcCS/SimpleHttpRequest.cs b/libsecondlife-cs/XmlRpcCS/SimpleHttpRequest.cs new file mode 100644 index 00000000..e5326c3d --- /dev/null +++ b/libsecondlife-cs/XmlRpcCS/SimpleHttpRequest.cs @@ -0,0 +1,204 @@ +namespace Nwc.XmlRpc +{ + using System; + using System.IO; + using System.Net.Sockets; + using System.Collections; + + ///Very basic HTTP request handler. + ///This class is designed to accept a TcpClient and treat it as an HTTP request. + /// It will do some basic header parsing and manage the input and output streams associated + /// with the request. + public class SimpleHttpRequest + { + private String _httpMethod = null; + private String _protocol; + private String _filePathFile = null; + private String _filePathDir = null; + private String __filePath; + private TcpClient _client; + private StreamReader _input; + private StreamWriter _output; + private Hashtable _headers; + + /// A constructor which accepts the TcpClient. + /// It creates the associated input and output streams, determines the request type, + /// and parses the remaining HTTP header. + /// The TcpClient associated with the HTTP connection. + public SimpleHttpRequest(TcpClient client) + { + _client = client; + _output = new StreamWriter(client.GetStream()); + _input = new StreamReader(client.GetStream()); + GetRequestMethod(); + GetRequestHeaders(); + } + + /// The output StreamWriter associated with the request. + public StreamWriter Output + { + get { return _output; } + } + + /// The input StreamReader associated with the request. + public StreamReader Input + { + get { return _input; } + } + + /// The TcpClient with the request. + public TcpClient Client + { + get { return _client; } + } + + private String _filePath + { + get { return __filePath; } + set + { + __filePath = value; + _filePathDir = null; + _filePathFile = null; + } + } + + /// The type of HTTP request (i.e. PUT, GET, etc.). + public String HttpMethod + { + get { return _httpMethod; } + } + + /// The level of the HTTP protocol. + public String Protocol + { + get { return _protocol; } + } + + /// The "path" which is part of any HTTP request. + public String FilePath + { + get { return _filePath; } + } + + /// The file portion of the "path" which is part of any HTTP request. + public String FilePathFile + { + get + { + if (_filePathFile != null) + return _filePathFile; + + int i = FilePath.LastIndexOf("/"); + + if (i == -1) + return ""; + + i++; + _filePathFile = FilePath.Substring(i, FilePath.Length - i); + return _filePathFile; + } + } + + /// The directory portion of the "path" which is part of any HTTP request. + public String FilePathDir + { + get + { + if (_filePathDir != null) + return _filePathDir; + + int i = FilePath.LastIndexOf("/"); + + if (i == -1) + return ""; + + i++; + _filePathDir = FilePath.Substring(0, i); + return _filePathDir; + } + } + + private void GetRequestMethod() + { + string req = _input.ReadLine(); + if (req == null) + throw new ApplicationException("Void request."); + + if (0 == String.Compare("GET ", req.Substring(0, 4), true)) + _httpMethod = "GET"; + else if (0 == String.Compare("POST ", req.Substring(0, 5), true)) + _httpMethod = "POST"; + else + throw new InvalidOperationException("Unrecognized method in query: " + req); + + req = req.TrimEnd(); + int idx = req.IndexOf(' ') + 1; + if (idx >= req.Length) + throw new ApplicationException("What do you want?"); + + string page_protocol = req.Substring(idx); + int idx2 = page_protocol.IndexOf(' '); + if (idx2 == -1) + idx2 = page_protocol.Length; + + _filePath = page_protocol.Substring(0, idx2).Trim(); + _protocol = page_protocol.Substring(idx2).Trim(); + } + + private void GetRequestHeaders() + { + String line; + int idx; + + _headers = new Hashtable(); + + while ((line = _input.ReadLine()) != "") + { + if (line == null) + { + break; + } + + idx = line.IndexOf(':'); + if (idx == -1 || idx == line.Length - 1) + { + Logger.WriteEntry("Malformed header line: " + line, LogLevel.Information); + continue; + } + + String key = line.Substring(0, idx); + String value = line.Substring(idx + 1); + + try + { + _headers.Add(key, value); + } + catch (Exception) + { + Logger.WriteEntry("Duplicate header key in line: " + line, LogLevel.Information); + } + } + } + + /// + /// Format the object contents into a useful string representation. + /// + ///String representation of the SimpleHttpRequest as the HttpMethod FilePath Protocol. + override public String ToString() + { + return HttpMethod + " " + FilePath + " " + Protocol; + } + + /// + /// Close the SimpleHttpRequest. This flushes and closes all associated io streams. + /// + public void Close() + { + _output.Flush(); + _output.Close(); + _input.Close(); + _client.Close(); + } + } +} diff --git a/libsecondlife-cs/XmlRpcCS/XmlRpcBoxcarRequest.cs b/libsecondlife-cs/XmlRpcCS/XmlRpcBoxcarRequest.cs new file mode 100644 index 00000000..f87f7a50 --- /dev/null +++ b/libsecondlife-cs/XmlRpcCS/XmlRpcBoxcarRequest.cs @@ -0,0 +1,51 @@ +namespace Nwc.XmlRpc +{ + using System; + using System.Collections; + using System.IO; + using System.Xml; + using System.Net; + using System.Text; + using System.Reflection; + + /// Class that collects individual XmlRpcRequest objects and submits them as a boxcarred request. + /// A boxcared request is when a number of request are collected before being sent via XML-RPC, and then are sent via + /// a single HTTP connection. This results in a speed up from reduced connection time. The results are then retuned collectively + /// as well. + /// + /// + public class XmlRpcBoxcarRequest : XmlRpcRequest + { + /// ArrayList to collect the requests to boxcar. + public IList Requests = new ArrayList(); + + /// Basic constructor. + public XmlRpcBoxcarRequest() + { + } + + /// Returns the String "system.multiCall" which is the server method that handles boxcars. + public override String MethodName + { + get { return "system.multiCall"; } + } + + /// The ArrayList of boxcarred Requests as properly formed parameters. + public override IList Params + { + get { + _params.Clear(); + ArrayList reqArray = new ArrayList(); + foreach (XmlRpcRequest request in Requests) + { + Hashtable requestEntry = new Hashtable(); + requestEntry.Add(XmlRpcXmlTokens.METHOD_NAME, request.MethodName); + requestEntry.Add(XmlRpcXmlTokens.PARAMS, request.Params); + reqArray.Add(requestEntry); + } + _params.Add(reqArray); + return _params; + } + } + } +} diff --git a/libsecondlife-cs/XmlRpcCS/XmlRpcClientProxy.cs b/libsecondlife-cs/XmlRpcCS/XmlRpcClientProxy.cs new file mode 100644 index 00000000..f52273a7 --- /dev/null +++ b/libsecondlife-cs/XmlRpcCS/XmlRpcClientProxy.cs @@ -0,0 +1,61 @@ +namespace Nwc.XmlRpc +{ + using System; + using System.Runtime.Remoting.Proxies; + using System.Runtime.Remoting.Messaging; + + /// This class provides support for creating local proxies of XML-RPC remote objects + /// + /// To create a local proxy you need to create a local C# interface and then, via createProxy + /// associate that interface with a remote object at a given URL. + /// +public class XmlRpcClientProxy : RealProxy +{ + private String _remoteObjectName; + private String _url; + private XmlRpcRequest _client = new XmlRpcRequest(); + + /// Factory method to create proxies. + /// + /// To create a local proxy you need to create a local C# interface with methods that mirror those of the server object. + /// Next, pass that interface into createProxy along with the object name and URL of the remote object and + /// cast the resulting object to the specifice interface. + /// + /// String The name of the remote object. + /// String The URL of the remote object. + /// Type The typeof() of a C# interface. + /// Object A proxy for your specified interface. Cast to appropriate type. + public static Object createProxy(String remoteObjectName, String url, Type anInterface) + { + return new XmlRpcClientProxy(remoteObjectName, url, anInterface).GetTransparentProxy(); + } + + private XmlRpcClientProxy(String remoteObjectName, String url, Type t) : base(t) + { + _remoteObjectName = remoteObjectName; + _url = url; + } + + /// The local method dispatcher - do not invoke. + override public IMessage Invoke(IMessage msg) + { + IMethodCallMessage methodMessage = (IMethodCallMessage)msg; + + _client.MethodName = _remoteObjectName + "." + methodMessage.MethodName; + _client.Params.Clear(); + foreach (Object o in methodMessage.Args) + _client.Params.Add(o); + + try + { + Object ret = _client.Invoke(_url); + return new ReturnMessage(ret,null,0, + methodMessage.LogicalCallContext, methodMessage); + } + catch (Exception e) + { + return new ReturnMessage(e, methodMessage); + } + } +} +} diff --git a/libsecondlife-cs/XmlRpcCS/XmlRpcDeserializer.cs b/libsecondlife-cs/XmlRpcCS/XmlRpcDeserializer.cs index cdd848a7..a17fdcbd 100644 --- a/libsecondlife-cs/XmlRpcCS/XmlRpcDeserializer.cs +++ b/libsecondlife-cs/XmlRpcCS/XmlRpcDeserializer.cs @@ -1,7 +1,7 @@ namespace Nwc.XmlRpc { using System; - using System.Collections.Generic; + using System.Collections; using System.IO; using System.Xml; using System.Globalization; @@ -22,7 +22,7 @@ namespace Nwc.XmlRpc private static DateTimeFormatInfo _dateFormat = new DateTimeFormatInfo(); private Object _container; - private Stack _containerStack; + private Stack _containerStack; /// Protected reference to last text. protected String _text; @@ -31,6 +31,7 @@ namespace Nwc.XmlRpc /// Protected reference to last name field. protected String _name; + /// Basic constructor. public XmlRpcDeserializer() { @@ -65,11 +66,11 @@ namespace Nwc.XmlRpc break; case STRUCT: PushContext(); - _container = new Dictionary(); + _container = new Hashtable(); break; case ARRAY: PushContext(); - _container = new List(); + _container = new ArrayList(); break; } break; @@ -92,7 +93,7 @@ namespace Nwc.XmlRpc _value = _text; break; case DOUBLE: - _value = Double.Parse(_text, CultureInfo.InvariantCulture); + _value = Double.Parse(_text); break; case INT: case ALT_INT: @@ -112,12 +113,12 @@ namespace Nwc.XmlRpc if (_value == null) _value = _text; // some kits don't use tag, they just do - if ((_container != null) && (_container is List)) // in an array? If so add value to it. - ((List)_container).Add(_value); + if ((_container != null) && (_container is IList)) // in an array? If so add value to it. + ((IList)_container).Add(_value); break; case MEMBER: - if ((_container != null) && (_container is Dictionary)) // in an struct? If so add value to it. - ((Dictionary)_container).Add(_name, _value); + if ((_container != null) && (_container is IDictionary)) // in an struct? If so add value to it. + ((IDictionary)_container).Add(_name, _value); break; case ARRAY: case STRUCT: @@ -172,8 +173,22 @@ namespace Nwc.XmlRpc _value = null; _name = null; _container = null; - _containerStack = new Stack(); + _containerStack = new Stack(); } + +#if __MONO__ + private DateTime DateParse(String str) + { + int year = Int32.Parse(str.Substring(0,4)); + int month = Int32.Parse(str.Substring(4,2)); + int day = Int32.Parse(str.Substring(6,2)); + int hour = Int32.Parse(str.Substring(9,2)); + int min = Int32.Parse(str.Substring(12,2)); + int sec = Int32.Parse(str.Substring(15,2)); + return new DateTime(year,month,day,hour,min,sec); + } +#endif + } } diff --git a/libsecondlife-cs/XmlRpcCS/XmlRpcErrorCodes.cs b/libsecondlife-cs/XmlRpcCS/XmlRpcErrorCodes.cs index f3bd13b4..a1097c51 100644 --- a/libsecondlife-cs/XmlRpcCS/XmlRpcErrorCodes.cs +++ b/libsecondlife-cs/XmlRpcCS/XmlRpcErrorCodes.cs @@ -1,15 +1,53 @@ namespace Nwc.XmlRpc { - using System; + using System; - /// Standard XML-RPC error codes - public class XmlRpcErrorCodes - { - // -32400 ---> system error - - /// Transport error - public const int TRANSPORT_ERROR = -32300; - /// Transport error message - public const String TRANSPORT_ERROR_MSG = "Transport Layer Error"; - } + /// Standard XML-RPC error codes. + public class XmlRpcErrorCodes + { + /// + public const int PARSE_ERROR_MALFORMED = -32700; + /// + public const String PARSE_ERROR_MALFORMED_MSG = "Parse Error, not well formed"; + + /// + public const int PARSE_ERROR_ENCODING = -32701; + /// + public const String PARSE_ERROR_ENCODING_MSG = "Parse Error, unsupported encoding"; + + /** + -32702 ---> parse error. invalid character for encoding + -32600 ---> server error. invalid xml-rpc. not conforming to spec. + **/ + + /// + public const int SERVER_ERROR_METHOD = -32601; + /// + public const String SERVER_ERROR_METHOD_MSG = "Server Error, requested method not found"; + + /// + public const int SERVER_ERROR_PARAMS = -32602; + /// + public const String SERVER_ERROR_PARAMS_MSG = "Server Error, invalid method parameters"; + + /** + -32603 ---> server error. internal xml-rpc error + **/ + + /// + public const int APPLICATION_ERROR = -32500; + /// + public const String APPLICATION_ERROR_MSG = "Application Error"; + + /** + -32400 ---> system error + **/ + + /// + public const int TRANSPORT_ERROR = -32300; + /// + public const String TRANSPORT_ERROR_MSG = "Transport Layer Error"; + } } + + diff --git a/libsecondlife-cs/XmlRpcCS/XmlRpcException.cs b/libsecondlife-cs/XmlRpcCS/XmlRpcException.cs index 333cfc0b..3d4f467a 100644 --- a/libsecondlife-cs/XmlRpcCS/XmlRpcException.cs +++ b/libsecondlife-cs/XmlRpcCS/XmlRpcException.cs @@ -1,38 +1,39 @@ namespace Nwc.XmlRpc { - using System; + using System; - /// An XML-RPC Exception. - /// Maps a C# exception to an XML-RPC fault. Normal exceptions - /// include a message so this adds the code needed by XML-RPC. - public class XmlRpcException : Exception + /// An XML-RPC Exception. + /// Maps a C# exception to an XML-RPC fault. Normal exceptions + /// include a message so this adds the code needed by XML-RPC. + public class XmlRpcException : Exception { - private int _code; - - /// Instantiate an XmlRpcException with a code and message. - /// Int faultCode associated with this exception. - /// String faultMessage associated with this exception. - public XmlRpcException(int code, String message) : base(message) - { - _code = code; - } + private int _code; - /// The value of the faults message, i.e. the faultString. - public String FaultString - { - get { return Message; } - } + /// Instantiate an XmlRpcException with a code and message. + /// Int faultCode associated with this exception. + /// String faultMessage associated with this exception. + public XmlRpcException(int code, String message) + : base(message) + { + _code = code; + } - /// The value of the faults code, i.e. the faultCode. - public int FaultCode - { - get { return _code; } - } + /// The value of the faults message, i.e. the faultString. + public String FaultString + { + get { return Message; } + } - /// Format the message to include the code. - override public String ToString() - { - return "Code: " + FaultCode + " Message: " + base.ToString(); - } + /// The value of the faults code, i.e. the faultCode. + public int FaultCode + { + get { return _code; } + } + + /// Format the message to include the code. + override public String ToString() + { + return "Code: " + FaultCode + " Message: " + base.ToString(); + } } } diff --git a/libsecondlife-cs/XmlRpcCS/XmlRpcExposedAttribute.cs b/libsecondlife-cs/XmlRpcCS/XmlRpcExposedAttribute.cs new file mode 100644 index 00000000..67b27aec --- /dev/null +++ b/libsecondlife-cs/XmlRpcCS/XmlRpcExposedAttribute.cs @@ -0,0 +1,60 @@ +namespace Nwc.XmlRpc +{ + using System; + using System.Reflection; + + /// + /// Simple tagging attribute to indicate participation is XML-RPC exposure. + /// + /// + /// If present at the class level it indicates that this class does explicitly + /// expose methods. If present at the method level it denotes that the method + /// is exposed. + /// + [AttributeUsage( + AttributeTargets.Class | AttributeTargets.Method, + AllowMultiple = false, + Inherited = true + )] + public class XmlRpcExposedAttribute : Attribute + { + /// Check if obj is an object utilizing the XML-RPC exposed Attribute. + /// Object of a class or method to check for attribute. + /// Boolean true if attribute present. + public static Boolean ExposedObject(Object obj) + { + return IsExposed(obj.GetType()); + } + + /// Check if obj.methodName is an XML-RPC exposed method. + /// A method is considered to be exposed if it exists and, either, the object does not use the XmlRpcExposed attribute, + /// or the object does use the XmlRpcExposed attribute and the method has the XmlRpcExposed attribute as well. + /// Boolean true if the method is exposed. + public static Boolean ExposedMethod(Object obj, String methodName) + { + Type type = obj.GetType(); + MethodInfo method = type.GetMethod(methodName); + + if (method == null) + throw new MissingMethodException("Method " + methodName + " not found."); + + if (!IsExposed(type)) + return true; + + return IsExposed(method); + } + + /// Check if mi is XML-RPC exposed. + /// MemberInfo of a class or method to check for attribute. + /// Boolean true if attribute present. + public static Boolean IsExposed(MemberInfo mi) + { + foreach (Attribute attr in mi.GetCustomAttributes(true)) + { + if (attr is XmlRpcExposedAttribute) + return true; + } + return false; + } + } +} diff --git a/libsecondlife-cs/XmlRpcCS/XmlRpcRequest.cs b/libsecondlife-cs/XmlRpcCS/XmlRpcRequest.cs index 3dc3f048..88267a9a 100644 --- a/libsecondlife-cs/XmlRpcCS/XmlRpcRequest.cs +++ b/libsecondlife-cs/XmlRpcCS/XmlRpcRequest.cs @@ -1,9 +1,7 @@ -#pragma warning disable 0618 - namespace Nwc.XmlRpc { using System; - using System.Collections.Generic; + using System.Collections; using System.IO; using System.Xml; using System.Net; @@ -36,16 +34,26 @@ namespace Nwc.XmlRpc private XmlRpcResponseDeserializer _deserializer = new XmlRpcResponseDeserializer(); /// ArrayList containing the parameters. - protected List _params = null; + protected IList _params = null; /// Instantiate an XmlRpcRequest public XmlRpcRequest() { - _params = new List(); + _params = new ArrayList(); } - /// ArrayList containing the parameters for the request. - public virtual List Params + /// Instantiate an XmlRpcRequest for a specified method and parameters. + /// String designating the object.method on the server the request + /// should be directed to. + /// ArrayList of XML-RPC type parameters to invoke the request with. + public XmlRpcRequest(String methodName, IList parameters) + { + MethodName = methodName; + _params = parameters; + } + + /// ArrayList conntaining the parameters for the request. + public virtual IList Params { get { return _params; } } @@ -57,16 +65,56 @@ namespace Nwc.XmlRpc set { _methodName = value; } } + /// String object name portion of the method name. + public String MethodNameObject + { + get + { + int index = MethodName.IndexOf("."); + + if (index == -1) + return MethodName; + + return MethodName.Substring(0, index); + } + } + + /// String method name portion of the object.method name. + public String MethodNameMethod + { + get + { + int index = MethodName.IndexOf("."); + + if (index == -1) + return MethodName; + + return MethodName.Substring(index + 1, MethodName.Length - index - 1); + } + } + + /// Invoke this request on the server. + /// String The url of the XML-RPC server. + /// Object The value returned from the method invocation on the server. + /// If an exception generated on the server side. + public Object Invoke(String url) + { + XmlRpcResponse res = Send(url, 10000); + + if (res.IsFault) + throw new XmlRpcException(res.FaultCode, res.FaultString); + + return res.Value; + } + /// Send the request to the server. /// String The url of the XML-RPC server. + /// Milliseconds before the connection times out. /// XmlRpcResponse The response generated. public XmlRpcResponse Send(String url, int timeout) { // Override SSL authentication mechanisms ServicePointManager.CertificatePolicy = new AcceptAllCertificatePolicy(); - //ServicePointManager.ServerCertificateValidationCallback += - // delegate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) - // { return true; }; HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); if (request == null) @@ -75,8 +123,7 @@ namespace Nwc.XmlRpc request.Method = "POST"; request.ContentType = "text/xml"; request.AllowWriteStreamBuffering = true; - request.KeepAlive = false; - request.Timeout = timeout; // miliseconds adjust as you see fit + request.Timeout = timeout; Stream stream = request.GetRequestStream(); XmlTextWriter xml = new XmlTextWriter(stream, _encoding); @@ -93,15 +140,6 @@ namespace Nwc.XmlRpc return resp; } - private bool CheckValidationResult(Object sender, - System.Security.Cryptography.X509Certificates.X509Certificate certificate, - System.Security.Cryptography.X509Certificates.X509Chain chain, - System.Net.Security.SslPolicyErrors sslPolicyErrors) - { - // Always accept - return true; - } - /// Produce String representation of the object. /// String representation of the object. override public String ToString() diff --git a/libsecondlife-cs/XmlRpcCS/XmlRpcRequestDeserializer.cs b/libsecondlife-cs/XmlRpcCS/XmlRpcRequestDeserializer.cs index 97a6046a..253f8913 100644 --- a/libsecondlife-cs/XmlRpcCS/XmlRpcRequestDeserializer.cs +++ b/libsecondlife-cs/XmlRpcCS/XmlRpcRequestDeserializer.cs @@ -1,64 +1,64 @@ namespace Nwc.XmlRpc { - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.IO; - using System.Xml; + using System; + using System.Collections; + using System.Diagnostics; + using System.IO; + using System.Xml; - /// Class to deserialize XML data representing a request. - public class XmlRpcRequestDeserializer : XmlRpcDeserializer - { - static private XmlRpcRequestDeserializer _singleton; - /// A static singleton instance of this deserializer. - [Obsolete("This object is now thread safe, just use an instance.",false)] - static public XmlRpcRequestDeserializer Singleton - { - get - { - if (_singleton == null) - _singleton = new XmlRpcRequestDeserializer(); + /// Class to deserialize XML data representing a request. + public class XmlRpcRequestDeserializer : XmlRpcDeserializer + { + static private XmlRpcRequestDeserializer _singleton; + /// A static singleton instance of this deserializer. + [Obsolete("This object is now thread safe, just use an instance.", false)] + static public XmlRpcRequestDeserializer Singleton + { + get + { + if (_singleton == null) + _singleton = new XmlRpcRequestDeserializer(); - return _singleton; - } - } + return _singleton; + } + } - /// Static method that parses XML data into a request using the Singleton. - /// StreamReader containing an XML-RPC request. - /// XmlRpcRequest object resulting from the parse. - override public Object Deserialize(TextReader xmlData) - { - XmlTextReader reader = new XmlTextReader(xmlData); - XmlRpcRequest request = new XmlRpcRequest(); - bool done = false; + /// Static method that parses XML data into a request using the Singleton. + /// StreamReader containing an XML-RPC request. + /// XmlRpcRequest object resulting from the parse. + override public Object Deserialize(TextReader xmlData) + { + XmlTextReader reader = new XmlTextReader(xmlData); + XmlRpcRequest request = new XmlRpcRequest(); + bool done = false; - lock(this) - { - Reset(); - while (!done && reader.Read()) - { - DeserializeNode(reader); // Parent parse... - switch (reader.NodeType) - { - case XmlNodeType.EndElement: - switch (reader.Name) - { - case METHOD_NAME: - request.MethodName = _text; - break; - case METHOD_CALL: - done = true; - break; - case PARAM: - request.Params.Add(_value); - _text = null; - break; - } - break; - } - } - } - return request; - } - } + lock (this) + { + Reset(); + while (!done && reader.Read()) + { + DeserializeNode(reader); // Parent parse... + switch (reader.NodeType) + { + case XmlNodeType.EndElement: + switch (reader.Name) + { + case METHOD_NAME: + request.MethodName = _text; + break; + case METHOD_CALL: + done = true; + break; + case PARAM: + request.Params.Add(_value); + _text = null; + break; + } + break; + } + } + } + return request; + } + } } diff --git a/libsecondlife-cs/XmlRpcCS/XmlRpcRequestSerializer.cs b/libsecondlife-cs/XmlRpcCS/XmlRpcRequestSerializer.cs index c66401d5..1451d699 100644 --- a/libsecondlife-cs/XmlRpcCS/XmlRpcRequestSerializer.cs +++ b/libsecondlife-cs/XmlRpcCS/XmlRpcRequestSerializer.cs @@ -1,38 +1,51 @@ namespace Nwc.XmlRpc { - using System; - using System.Collections.Generic; - using System.Xml; - using System.IO; + using System; + using System.Collections; + using System.Xml; + using System.IO; - /// Class responsible for serializing an XML-RPC request. - /// This class handles the request envelope, depending on XmlRpcSerializer - /// to serialize the payload. - /// - public class XmlRpcRequestSerializer : XmlRpcSerializer - { - /// Serialize the XmlRpcRequest to the output stream. - /// An XmlTextWriter stream to write data to. - /// An XmlRpcRequest to serialize. - /// - override public void Serialize(XmlTextWriter output, Object obj) - { - XmlRpcRequest request = (XmlRpcRequest)obj; - output.WriteStartDocument(); - output.WriteStartElement(METHOD_CALL); - output.WriteElementString(METHOD_NAME,request.MethodName); - output.WriteStartElement(PARAMS); - foreach (Object param in request.Params) - { - output.WriteStartElement(PARAM); - output.WriteStartElement(VALUE); - SerializeObject(output, param); - output.WriteEndElement(); - output.WriteEndElement(); - } + /// Class responsible for serializing an XML-RPC request. + /// This class handles the request envelope, depending on XmlRpcSerializer + /// to serialize the payload. + /// + public class XmlRpcRequestSerializer : XmlRpcSerializer + { + static private XmlRpcRequestSerializer _singleton; + /// A static singleton instance of this deserializer. + static public XmlRpcRequestSerializer Singleton + { + get + { + if (_singleton == null) + _singleton = new XmlRpcRequestSerializer(); - output.WriteEndElement(); - output.WriteEndElement(); - } - } + return _singleton; + } + } + + /// Serialize the XmlRpcRequest to the output stream. + /// An XmlTextWriter stream to write data to. + /// An XmlRpcRequest to serialize. + /// + override public void Serialize(XmlTextWriter output, Object obj) + { + XmlRpcRequest request = (XmlRpcRequest)obj; + output.WriteStartDocument(); + output.WriteStartElement(METHOD_CALL); + output.WriteElementString(METHOD_NAME, request.MethodName); + output.WriteStartElement(PARAMS); + foreach (Object param in request.Params) + { + output.WriteStartElement(PARAM); + output.WriteStartElement(VALUE); + SerializeObject(output, param); + output.WriteEndElement(); + output.WriteEndElement(); + } + + output.WriteEndElement(); + output.WriteEndElement(); + } + } } diff --git a/libsecondlife-cs/XmlRpcCS/XmlRpcResponder.cs b/libsecondlife-cs/XmlRpcCS/XmlRpcResponder.cs new file mode 100644 index 00000000..04125689 --- /dev/null +++ b/libsecondlife-cs/XmlRpcCS/XmlRpcResponder.cs @@ -0,0 +1,98 @@ +namespace Nwc.XmlRpc +{ + using System; + using System.Xml; + using System.Net.Sockets; + + /// The class is a container of the context of an XML-RPC dialog on the server side. + /// Instances of this class maintain the context for an individual XML-RPC server + /// side dialog. Namely they manage an inbound deserializer and an outbound serializer. + public class XmlRpcResponder + { + private XmlRpcRequestDeserializer _deserializer = new XmlRpcRequestDeserializer(); + private XmlRpcResponseSerializer _serializer = new XmlRpcResponseSerializer(); + private XmlRpcServer _server; + private TcpClient _client; + private SimpleHttpRequest _httpReq; + + /// The SimpleHttpRequest based on the TcpClient. + public SimpleHttpRequest HttpReq + { + get { return _httpReq; } + } + + /// Basic constructor. + /// XmlRpcServer that this XmlRpcResponder services. + /// TcpClient with the connection. + public XmlRpcResponder(XmlRpcServer server, TcpClient client) + { + _server = server; + _client = client; + _httpReq = new SimpleHttpRequest(_client); + } + + /// Call close to insure proper shutdown. + ~XmlRpcResponder() + { + Close(); + } + + ///Respond using this responders HttpReq. + public void Respond() + { + Respond(HttpReq); + } + + /// Handle an HTTP request containing an XML-RPC request. + /// This method deserializes the XML-RPC request, invokes the + /// described method, serializes the response (or fault) and sends the XML-RPC response + /// back as a valid HTTP page. + /// + /// SimpleHttpRequest containing the request. + public void Respond(SimpleHttpRequest httpReq) + { + XmlRpcRequest xmlRpcReq = (XmlRpcRequest)_deserializer.Deserialize(httpReq.Input); + XmlRpcResponse xmlRpcResp = new XmlRpcResponse(); + + try + { + xmlRpcResp.Value = _server.Invoke(xmlRpcReq); + } + catch (XmlRpcException e) + { + xmlRpcResp.SetFault(e.FaultCode, e.FaultString); + } + catch (Exception e2) + { + xmlRpcResp.SetFault(XmlRpcErrorCodes.APPLICATION_ERROR, + XmlRpcErrorCodes.APPLICATION_ERROR_MSG + ": " + e2.Message); + } + + if (Logger.Delegate != null) + Logger.WriteEntry(xmlRpcResp.ToString(), LogLevel.Information); + + XmlRpcServer.HttpHeader(httpReq.Protocol, "text/xml", 0, " 200 OK", httpReq.Output); + httpReq.Output.Flush(); + XmlTextWriter xml = new XmlTextWriter(httpReq.Output); + _serializer.Serialize(xml, xmlRpcResp); + xml.Flush(); + httpReq.Output.Flush(); + } + + ///Close all contained resources, both the HttpReq and client. + public void Close() + { + if (_httpReq != null) + { + _httpReq.Close(); + _httpReq = null; + } + + if (_client != null) + { + _client.Close(); + _client = null; + } + } + } +} diff --git a/libsecondlife-cs/XmlRpcCS/XmlRpcResponse.cs b/libsecondlife-cs/XmlRpcCS/XmlRpcResponse.cs index 536ac150..0d7d985c 100644 --- a/libsecondlife-cs/XmlRpcCS/XmlRpcResponse.cs +++ b/libsecondlife-cs/XmlRpcCS/XmlRpcResponse.cs @@ -1,7 +1,7 @@ namespace Nwc.XmlRpc { using System; - using System.Collections.Generic; + using System.Collections; using System.IO; using System.Xml; @@ -19,6 +19,15 @@ namespace Nwc.XmlRpc IsFault = false; } + /// Constructor for a fault. + /// int the numeric faultCode value. + /// String the faultString value. + public XmlRpcResponse(int code, String message) + : this() + { + SetFault(code, message); + } + /// The data value of the response, may be fault data. public Object Value { @@ -38,7 +47,7 @@ namespace Nwc.XmlRpc if (!IsFault) return 0; else - return (int)((Dictionary)_value)[XmlRpcXmlTokens.FAULT_CODE]; + return (int)((Hashtable)_value)[XmlRpcXmlTokens.FAULT_CODE]; } } @@ -50,7 +59,7 @@ namespace Nwc.XmlRpc if (!IsFault) return ""; else - return (String)((Dictionary)_value)[XmlRpcXmlTokens.FAULT_STRING]; + return (String)((Hashtable)_value)[XmlRpcXmlTokens.FAULT_STRING]; } } @@ -59,7 +68,7 @@ namespace Nwc.XmlRpc /// String the faultString value. public void SetFault(int code, String message) { - Dictionary fault = new Dictionary(); + Hashtable fault = new Hashtable(); fault.Add("faultCode", code); fault.Add("faultString", message); Value = fault; diff --git a/libsecondlife-cs/XmlRpcCS/XmlRpcResponseDeserializer.cs b/libsecondlife-cs/XmlRpcCS/XmlRpcResponseDeserializer.cs index df48c15e..bfaf968e 100644 --- a/libsecondlife-cs/XmlRpcCS/XmlRpcResponseDeserializer.cs +++ b/libsecondlife-cs/XmlRpcCS/XmlRpcResponseDeserializer.cs @@ -1,51 +1,65 @@ namespace Nwc.XmlRpc { - using System; - using System.Collections.Generic; - using System.IO; - using System.Xml; + using System; + using System.Collections; + using System.IO; + using System.Xml; - /// Class to deserialize XML data representing a response. - public class XmlRpcResponseDeserializer : XmlRpcDeserializer - { - /// Static method that parses XML data into a response using the Singleton. - /// StreamReader containing an XML-RPC response. - /// XmlRpcResponse object resulting from the parse. - override public Object Deserialize(TextReader xmlData) - { - XmlTextReader reader = new XmlTextReader(xmlData); - XmlRpcResponse response = new XmlRpcResponse(); - bool done = false; - - lock(this) - { - Reset(); - - while (!done && reader.Read()) - { - DeserializeNode(reader); // Parent parse... - switch (reader.NodeType) - { - case XmlNodeType.EndElement: - switch (reader.Name) - { - case FAULT: - response.Value = _value; - response.IsFault = true; - break; - case PARAM: - response.Value = _value; - _value = null; - _text = null; - break; - } - break; - default: - break; - } - } - } - return response; - } - } + /// Class to deserialize XML data representing a response. + public class XmlRpcResponseDeserializer : XmlRpcDeserializer + { + static private XmlRpcResponseDeserializer _singleton; + /// A static singleton instance of this deserializer. + [Obsolete("This object is now thread safe, just use an instance.", false)] + static public XmlRpcResponseDeserializer Singleton + { + get + { + if (_singleton == null) + _singleton = new XmlRpcResponseDeserializer(); + + return _singleton; + } + } + + /// Static method that parses XML data into a response using the Singleton. + /// StreamReader containing an XML-RPC response. + /// XmlRpcResponse object resulting from the parse. + override public Object Deserialize(TextReader xmlData) + { + XmlTextReader reader = new XmlTextReader(xmlData); + XmlRpcResponse response = new XmlRpcResponse(); + bool done = false; + + lock (this) + { + Reset(); + + while (!done && reader.Read()) + { + DeserializeNode(reader); // Parent parse... + switch (reader.NodeType) + { + case XmlNodeType.EndElement: + switch (reader.Name) + { + case FAULT: + response.Value = _value; + response.IsFault = true; + break; + case PARAM: + response.Value = _value; + _value = null; + _text = null; + break; + } + break; + default: + break; + } + } + } + return response; + } + } } diff --git a/libsecondlife-cs/XmlRpcCS/XmlRpcResponseSerializer.cs b/libsecondlife-cs/XmlRpcCS/XmlRpcResponseSerializer.cs index 5a7010c0..67b3b096 100644 --- a/libsecondlife-cs/XmlRpcCS/XmlRpcResponseSerializer.cs +++ b/libsecondlife-cs/XmlRpcCS/XmlRpcResponseSerializer.cs @@ -1,57 +1,57 @@ namespace Nwc.XmlRpc { - using System; - using System.Collections.Generic; - using System.Xml; + using System; + using System.Collections; + using System.Xml; - /// Class responsible for serializing an XML-RPC response. - /// This class handles the response envelope, depending on XmlRpcSerializer - /// to serialize the payload. - /// - public class XmlRpcResponseSerializer : XmlRpcSerializer - { - static private XmlRpcResponseSerializer _singleton; - /// A static singleton instance of this deserializer. - static public XmlRpcResponseSerializer Singleton - { - get - { - if (_singleton == null) - _singleton = new XmlRpcResponseSerializer(); + /// Class responsible for serializing an XML-RPC response. + /// This class handles the response envelope, depending on XmlRpcSerializer + /// to serialize the payload. + /// + public class XmlRpcResponseSerializer : XmlRpcSerializer + { + static private XmlRpcResponseSerializer _singleton; + /// A static singleton instance of this deserializer. + static public XmlRpcResponseSerializer Singleton + { + get + { + if (_singleton == null) + _singleton = new XmlRpcResponseSerializer(); - return _singleton; - } - } + return _singleton; + } + } - /// Serialize the XmlRpcResponse to the output stream. - /// An XmlTextWriter stream to write data to. - /// An Object to serialize. - /// - override public void Serialize(XmlTextWriter output, Object obj) - { - XmlRpcResponse response = (XmlRpcResponse) obj; + /// Serialize the XmlRpcResponse to the output stream. + /// An XmlTextWriter stream to write data to. + /// An Object to serialize. + /// + override public void Serialize(XmlTextWriter output, Object obj) + { + XmlRpcResponse response = (XmlRpcResponse)obj; - output.WriteStartDocument(); - output.WriteStartElement(METHOD_RESPONSE); + output.WriteStartDocument(); + output.WriteStartElement(METHOD_RESPONSE); - if (response.IsFault) - output.WriteStartElement(FAULT); - else - { - output.WriteStartElement(PARAMS); - output.WriteStartElement(PARAM); - } + if (response.IsFault) + output.WriteStartElement(FAULT); + else + { + output.WriteStartElement(PARAMS); + output.WriteStartElement(PARAM); + } - output.WriteStartElement(VALUE); + output.WriteStartElement(VALUE); - SerializeObject(output,response.Value); + SerializeObject(output, response.Value); - output.WriteEndElement(); + output.WriteEndElement(); - output.WriteEndElement(); - if (!response.IsFault) - output.WriteEndElement(); - output.WriteEndElement(); - } - } + output.WriteEndElement(); + if (!response.IsFault) + output.WriteEndElement(); + output.WriteEndElement(); + } + } } diff --git a/libsecondlife-cs/XmlRpcCS/XmlRpcSerializer.cs b/libsecondlife-cs/XmlRpcCS/XmlRpcSerializer.cs index 98ee67d4..8989a5b0 100644 --- a/libsecondlife-cs/XmlRpcCS/XmlRpcSerializer.cs +++ b/libsecondlife-cs/XmlRpcCS/XmlRpcSerializer.cs @@ -1,7 +1,7 @@ namespace Nwc.XmlRpc { using System; - using System.Collections.Generic; + using System.Collections; using System.IO; using System.Xml; @@ -72,13 +72,13 @@ namespace Nwc.XmlRpc { output.WriteElementString(BOOLEAN, ((((Boolean)obj) == true) ? "1" : "0")); } - else if (obj is List) + else if (obj is IList) { output.WriteStartElement(ARRAY); output.WriteStartElement(DATA); - if (((List)obj).Count > 0) + if (((ArrayList)obj).Count > 0) { - foreach (Object member in ((List)obj)) + foreach (Object member in ((IList)obj)) { output.WriteStartElement(VALUE); SerializeObject(output, member); @@ -88,9 +88,9 @@ namespace Nwc.XmlRpc output.WriteEndElement(); output.WriteEndElement(); } - else if (obj is Dictionary) + else if (obj is IDictionary) { - Dictionary h = (Dictionary)obj; + IDictionary h = (IDictionary)obj; output.WriteStartElement(STRUCT); foreach (String key in h.Keys) { diff --git a/libsecondlife-cs/XmlRpcCS/XmlRpcServer.cs b/libsecondlife-cs/XmlRpcCS/XmlRpcServer.cs new file mode 100644 index 00000000..8f1b10f8 --- /dev/null +++ b/libsecondlife-cs/XmlRpcCS/XmlRpcServer.cs @@ -0,0 +1,237 @@ +namespace Nwc.XmlRpc +{ + using System; + using System.Collections; + using System.IO; + using System.Net; + using System.Net.Sockets; + using System.Text; + using System.Threading; + using System.Xml; + + /// A restricted HTTP server for use with XML-RPC. + /// It only handles POST requests, and only POSTs representing XML-RPC calls. + /// In addition to dispatching requests it also provides a registry for request handlers. + /// + public class XmlRpcServer : IEnumerable + { + const int RESPONDER_COUNT = 10; + private TcpListener _myListener; + private int _port; + private IPAddress _address; + private IDictionary _handlers; + private XmlRpcSystemObject _system; + private WaitCallback _wc; + + ///Constructor with port and address. + ///This constructor sets up a TcpListener listening on the + ///given port and address. It also calls a Thread on the method StartListen(). + ///IPAddress value of the address to listen on. + ///Int value of the port to listen on. + public XmlRpcServer(IPAddress address, int port) + { + _port = port; + _address = address; + _handlers = new Hashtable(); + _system = new XmlRpcSystemObject(this); + _wc = new WaitCallback(WaitCallback); + } + + ///Basic constructor. + ///This constructor sets up a TcpListener listening on the + ///given port. It also calls a Thread on the method StartListen(). IPAddress.Any + ///is assumed as the address here. + ///Int value of the port to listen on. + public XmlRpcServer(int port) : this(IPAddress.Any, port) { } + + /// Start the server. + public void Start() + { + try + { + Stop(); + //start listing on the given port + // IPAddress addr = IPAddress.Parse("127.0.0.1"); + lock (this) + { + _myListener = new TcpListener(IPAddress.Any, _port); + _myListener.Start(); + //start the thread which calls the method 'StartListen' + Thread th = new Thread(new ThreadStart(StartListen)); + th.Start(); + } + } + catch (Exception e) + { + Logger.WriteEntry("An Exception Occurred while Listening :" + e.ToString(), LogLevel.Error); + } + } + + /// Stop the server. + public void Stop() + { + try + { + if (_myListener != null) + { + lock (this) + { + _myListener.Stop(); + _myListener = null; + } + } + } + catch (Exception e) + { + Logger.WriteEntry("An Exception Occurred while stopping :" + + e.ToString(), LogLevel.Error); + } + } + + /// Get an enumeration of my XML-RPC handlers. + /// IEnumerable the handler enumeration. + public IEnumerator GetEnumerator() + { + return _handlers.GetEnumerator(); + } + + /// Retrieve a handler by name. + /// String naming a handler + /// Object that is the handler. + public Object this[String name] + { + get { return _handlers[name]; } + } + + /// + ///This method Accepts new connections and dispatches them when appropriate. + /// + public void StartListen() + { + while (true && _myListener != null) + { + //Accept a new connection + XmlRpcResponder responder = new XmlRpcResponder(this, _myListener.AcceptTcpClient()); + ThreadPool.QueueUserWorkItem(_wc, responder); + } + } + + + /// + ///Add an XML-RPC handler object by name. + /// + ///String XML-RPC dispatch name of this object. + ///Object The object that is the XML-RPC handler. + public void Add(String name, Object obj) + { + _handlers.Add(name, obj); + } + + ///Return a C# object.method name for and XML-RPC object.method name pair. + ///The XML-RPC object.method. + ///String of form object.method for the underlying C# method. + public String MethodName(String methodName) + { + int dotAt = methodName.LastIndexOf('.'); + + if (dotAt == -1) + { + throw new XmlRpcException(XmlRpcErrorCodes.SERVER_ERROR_METHOD, + XmlRpcErrorCodes.SERVER_ERROR_METHOD_MSG + ": Bad method name " + methodName); + } + + String objectName = methodName.Substring(0, dotAt); + Object target = _handlers[objectName]; + + if (target == null) + { + throw new XmlRpcException(XmlRpcErrorCodes.SERVER_ERROR_METHOD, + XmlRpcErrorCodes.SERVER_ERROR_METHOD_MSG + ": Object " + objectName + " not found"); + } + + return target.GetType().FullName + "." + methodName.Substring(dotAt + 1); + } + + ///Invoke a method described in a request. + ///XmlRpcRequest containing a method descriptions. + /// + /// + public Object Invoke(XmlRpcRequest req) + { + return Invoke(req.MethodNameObject, req.MethodNameMethod, req.Params); + } + + ///Invoke a method on a named handler. + ///String The name of the handler. + ///String The name of the method to invoke on the handler. + ///IList The parameters to invoke the method with. + /// + public Object Invoke(String objectName, String methodName, IList parameters) + { + Object target = _handlers[objectName]; + + if (target == null) + { + throw new XmlRpcException(XmlRpcErrorCodes.SERVER_ERROR_METHOD, + XmlRpcErrorCodes.SERVER_ERROR_METHOD_MSG + ": Object " + objectName + " not found"); + } + + return XmlRpcSystemObject.Invoke(target, methodName, parameters); + } + + /// The method the thread pool invokes when a thread is available to handle an HTTP request. + /// TcpClient from the socket accept. + public void WaitCallback(object responder) + { + XmlRpcResponder resp = (XmlRpcResponder)responder; + + if (resp.HttpReq.HttpMethod == "POST") + { + try + { + resp.Respond(); + } + catch (Exception e) + { + Logger.WriteEntry("Failed on post: " + e, LogLevel.Error); + } + } + else + { + Logger.WriteEntry("Only POST methods are supported: " + resp.HttpReq.HttpMethod + + " ignored", LogLevel.Error); + } + + resp.Close(); + } + + /// + /// This function send the Header Information to the client (Browser) + /// + /// HTTP Version + /// Mime Type + /// Total Bytes to be sent in the body + /// + /// Socket reference + static public void HttpHeader(string sHttpVersion, string sMIMEHeader, long iTotBytes, string sStatusCode, TextWriter output) + { + String sBuffer = ""; + + // if Mime type is not provided set default to text/html + if (sMIMEHeader.Length == 0) + { + sMIMEHeader = "text/html"; // Default Mime Type is text/html + } + + sBuffer += sHttpVersion + sStatusCode + "\r\n"; + sBuffer += "Connection: close\r\n"; + if (iTotBytes > 0) + sBuffer += "Content-Length: " + iTotBytes + "\r\n"; + sBuffer += "Server: XmlRpcServer \r\n"; + sBuffer += "Content-Type: " + sMIMEHeader + "\r\n"; + sBuffer += "\r\n"; + + output.Write(sBuffer); + } + } +} diff --git a/libsecondlife-cs/XmlRpcCS/XmlRpcSystemObject.cs b/libsecondlife-cs/XmlRpcCS/XmlRpcSystemObject.cs new file mode 100644 index 00000000..5f799514 --- /dev/null +++ b/libsecondlife-cs/XmlRpcCS/XmlRpcSystemObject.cs @@ -0,0 +1,252 @@ +namespace Nwc.XmlRpc +{ + using System; + using System.Collections; + using System.Reflection; + + /// XML-RPC System object implementation of extended specifications. + [XmlRpcExposed] + public class XmlRpcSystemObject + { + private XmlRpcServer _server; + static private IDictionary _methodHelp = new Hashtable(); + + /// Static IDictionary to hold mappings of method name to associated documentation String + static public IDictionary MethodHelp + { + get { return _methodHelp; } + } + + /// Constructor. + /// XmlRpcServer server to be the system object for. + public XmlRpcSystemObject(XmlRpcServer server) + { + _server = server; + server.Add("system", this); + _methodHelp.Add(this.GetType().FullName + ".methodHelp", "Return a string description."); + } + + /// Invoke a method on a given object. + /// Using reflection, and respecting the XmlRpcExposed attribute, + /// invoke the methodName method on the target + /// instance with the parameters provided. All this packages other Invoke methods + /// end up calling this. + /// Object the value the invoked method returns. + /// If method does not exist, is not exposed, parameters invalid, or invocation + /// results in an exception. Note, the XmlRpcException.Code will indicate cause. + static public Object Invoke(Object target, String methodName, IList parameters) + { + if (target == null) + throw new XmlRpcException(XmlRpcErrorCodes.SERVER_ERROR_METHOD, + XmlRpcErrorCodes.SERVER_ERROR_METHOD_MSG + ": Invalid target object."); + + Type type = target.GetType(); + MethodInfo method = type.GetMethod(methodName); + + try + { + if (!XmlRpcExposedAttribute.ExposedMethod(target, methodName)) + throw new XmlRpcException(XmlRpcErrorCodes.SERVER_ERROR_METHOD, + XmlRpcErrorCodes.SERVER_ERROR_METHOD_MSG + ": Method " + methodName + " is not exposed."); + } + catch (MissingMethodException me) + { + throw new XmlRpcException(XmlRpcErrorCodes.SERVER_ERROR_METHOD, + XmlRpcErrorCodes.SERVER_ERROR_METHOD_MSG + ": " + me.Message); + } + + Object[] args = new Object[parameters.Count]; + + int index = 0; + foreach (Object arg in parameters) + { + args[index] = arg; + index++; + } + + try + { + Object retValue = method.Invoke(target, args); + if (retValue == null) + throw new XmlRpcException(XmlRpcErrorCodes.APPLICATION_ERROR, + XmlRpcErrorCodes.APPLICATION_ERROR_MSG + ": Method returned NULL."); + return retValue; + } + catch (XmlRpcException e) + { + throw e; + } + catch (ArgumentException ae) + { + Logger.WriteEntry(XmlRpcErrorCodes.SERVER_ERROR_PARAMS_MSG + ": " + ae.Message, + LogLevel.Information); + String call = methodName + "( "; + foreach (Object o in args) + { + call += o.GetType().Name; + call += " "; + } + call += ")"; + throw new XmlRpcException(XmlRpcErrorCodes.SERVER_ERROR_PARAMS, + XmlRpcErrorCodes.SERVER_ERROR_PARAMS_MSG + ": Arguement type mismatch invoking " + call); + } + catch (TargetParameterCountException tpce) + { + Logger.WriteEntry(XmlRpcErrorCodes.SERVER_ERROR_PARAMS_MSG + ": " + tpce.Message, + LogLevel.Information); + throw new XmlRpcException(XmlRpcErrorCodes.SERVER_ERROR_PARAMS, + XmlRpcErrorCodes.SERVER_ERROR_PARAMS_MSG + ": Arguement count mismatch invoking " + methodName); + } + catch (TargetInvocationException tie) + { + throw new XmlRpcException(XmlRpcErrorCodes.APPLICATION_ERROR, + XmlRpcErrorCodes.APPLICATION_ERROR_MSG + " Invoked method " + methodName + ": " + tie.Message); + } + } + + /// List methods available on all handlers of this server. + /// IList An array of Strings, each String will have form "object.method". + [XmlRpcExposed] + public IList listMethods() + { + IList methods = new ArrayList(); + Boolean considerExposure; + + foreach (DictionaryEntry handlerEntry in _server) + { + considerExposure = XmlRpcExposedAttribute.IsExposed(handlerEntry.Value.GetType()); + + foreach (MemberInfo mi in handlerEntry.Value.GetType().GetMembers()) + { + if (mi.MemberType != MemberTypes.Method) + continue; + + if (!((MethodInfo)mi).IsPublic) + continue; + + if (considerExposure && !XmlRpcExposedAttribute.IsExposed(mi)) + continue; + + methods.Add(handlerEntry.Key + "." + mi.Name); + } + } + + return methods; + } + + /// Given a method name return the possible signatures for it. + /// String The object.method name to look up. + /// IList Of arrays of signatures. + [XmlRpcExposed] + public IList methodSignature(String name) + { + IList signatures = new ArrayList(); + int index = name.IndexOf('.'); + + if (index < 0) + return signatures; + + String oName = name.Substring(0, index); + Object obj = _server[oName]; + + if (obj == null) + return signatures; + + MemberInfo[] mi = obj.GetType().GetMember(name.Substring(index + 1)); + + if (mi == null || mi.Length != 1) // for now we want a single signature + return signatures; + + MethodInfo method; + + try + { + method = (MethodInfo)mi[0]; + } + catch (Exception e) + { + Logger.WriteEntry("Attempted methodSignature call on " + mi[0] + " caused: " + e, + LogLevel.Information); + return signatures; + } + + if (!method.IsPublic) + return signatures; + + IList signature = new ArrayList(); + signature.Add(method.ReturnType.Name); + + foreach (ParameterInfo param in method.GetParameters()) + { + signature.Add(param.ParameterType.Name); + } + + + signatures.Add(signature); + + return signatures; + } + + /// Help for given method signature. Not implemented yet. + /// String The object.method name to look up. + /// String help text. Rich HTML text. + [XmlRpcExposed] + public String methodHelp(String name) + { + String help = null; + + try + { + help = (String)_methodHelp[_server.MethodName(name)]; + } + catch (XmlRpcException e) + { + throw e; + } + catch (Exception) { /* ignored */ }; + + if (help == null) + help = "No help available for: " + name; + + return help; + } + + /// Boxcarring support method. + /// IList of calls + /// ArrayList of results/faults. + [XmlRpcExposed] + public IList multiCall(IList calls) + { + IList responses = new ArrayList(); + XmlRpcResponse fault = new XmlRpcResponse(); + + foreach (IDictionary call in calls) + { + try + { + XmlRpcRequest req = new XmlRpcRequest((String)call[XmlRpcXmlTokens.METHOD_NAME], + (ArrayList)call[XmlRpcXmlTokens.PARAMS]); + Object results = _server.Invoke(req); + IList response = new ArrayList(); + response.Add(results); + responses.Add(response); + } + catch (XmlRpcException e) + { + fault.SetFault(e.FaultCode, e.FaultString); + responses.Add(fault.Value); + } + catch (Exception e2) + { + fault.SetFault(XmlRpcErrorCodes.APPLICATION_ERROR, + XmlRpcErrorCodes.APPLICATION_ERROR_MSG + ": " + e2.Message); + responses.Add(fault.Value); + } + } + + return responses; + } + + } +} + diff --git a/libsecondlife-cs/XmlRpcCS/XmlRpcXmlTokens.cs b/libsecondlife-cs/XmlRpcCS/XmlRpcXmlTokens.cs index 05ea0eb8..6fc60af0 100644 --- a/libsecondlife-cs/XmlRpcCS/XmlRpcXmlTokens.cs +++ b/libsecondlife-cs/XmlRpcCS/XmlRpcXmlTokens.cs @@ -1,76 +1,76 @@ namespace Nwc.XmlRpc { - using System; + using System; - /// Class collecting String tokens that are part of XML-RPC files. - public class XmlRpcXmlTokens - { - /// C# formatting string to describe an ISO 8601 date. - public const String ISO_DATETIME = "yyyyMMdd\\THH\\:mm\\:ss"; - /// Base64 field indicator. - /// Corresponds to the <base64> tag. - public const String BASE64 = "base64"; - /// String field indicator. - /// Corresponds to the <string> tag. - public const String STRING = "string"; - /// Integer field integer. - /// Corresponds to the <i4> tag. - public const String INT = "i4"; - /// Alternate integer field indicator. - /// Corresponds to the <int> tag. - public const String ALT_INT = "int"; - /// Date field indicator. - /// Corresponds to the <dateTime.iso8601> tag. - public const String DATETIME = "dateTime.iso8601"; - /// Boolean field indicator. - /// Corresponds to the <boolean> tag. - public const String BOOLEAN = "boolean"; - /// Value token. - /// Corresponds to the <value> tag. - public const String VALUE = "value"; - /// Name token. - /// Corresponds to the <name> tag. - public const String NAME = "name"; - /// Array field indicator.. - /// Corresponds to the <array> tag. - public const String ARRAY = "array"; - /// Data token. - /// Corresponds to the <data> tag. - public const String DATA = "data"; - /// Member token. - /// Corresponds to the <member> tag. - public const String MEMBER = "member"; - /// Stuct field indicator. - /// Corresponds to the <struct> tag. - public const String STRUCT = "struct"; - /// Double field indicator. - /// Corresponds to the <double> tag. - public const String DOUBLE = "double"; - /// Param token. - /// Corresponds to the <param> tag. - public const String PARAM = "param"; - /// Params token. - /// Corresponds to the <params> tag. - public const String PARAMS = "params"; - /// MethodCall token. - /// Corresponds to the <methodCall> tag. - public const String METHOD_CALL = "methodCall"; - /// MethodName token. - /// Corresponds to the <methodName> tag. - public const String METHOD_NAME = "methodName"; - /// MethodResponse token - /// Corresponds to the <methodResponse> tag. - public const String METHOD_RESPONSE = "methodResponse"; - /// Fault response token. - /// Corresponds to the <fault> tag. - public const String FAULT = "fault"; - /// FaultCode token. - /// Corresponds to the <faultCode> tag. - public const String FAULT_CODE = "faultCode"; - /// FaultString token. - /// Corresponds to the <faultString> tag. - public const String FAULT_STRING = "faultString"; - } + /// Class collecting String tokens that are part of XML-RPC files. + public class XmlRpcXmlTokens + { + /// C# formatting string to describe an ISO 8601 date. + public const String ISO_DATETIME = "yyyyMMdd\\THH\\:mm\\:ss"; + /// Base64 field indicator. + /// Corresponds to the <base64> tag. + public const String BASE64 = "base64"; + /// String field indicator. + /// Corresponds to the <string> tag. + public const String STRING = "string"; + /// Integer field integer. + /// Corresponds to the <i4> tag. + public const String INT = "i4"; + /// Alternate integer field indicator. + /// Corresponds to the <int> tag. + public const String ALT_INT = "int"; + /// Date field indicator. + /// Corresponds to the <dateTime.iso8601> tag. + public const String DATETIME = "dateTime.iso8601"; + /// Boolean field indicator. + /// Corresponds to the <boolean> tag. + public const String BOOLEAN = "boolean"; + /// Value token. + /// Corresponds to the <value> tag. + public const String VALUE = "value"; + /// Name token. + /// Corresponds to the <name> tag. + public const String NAME = "name"; + /// Array field indicator.. + /// Corresponds to the <array> tag. + public const String ARRAY = "array"; + /// Data token. + /// Corresponds to the <data> tag. + public const String DATA = "data"; + /// Member token. + /// Corresponds to the <member> tag. + public const String MEMBER = "member"; + /// Stuct field indicator. + /// Corresponds to the <struct> tag. + public const String STRUCT = "struct"; + /// Double field indicator. + /// Corresponds to the <double> tag. + public const String DOUBLE = "double"; + /// Param token. + /// Corresponds to the <param> tag. + public const String PARAM = "param"; + /// Params token. + /// Corresponds to the <params> tag. + public const String PARAMS = "params"; + /// MethodCall token. + /// Corresponds to the <methodCall> tag. + public const String METHOD_CALL = "methodCall"; + /// MethodName token. + /// Corresponds to the <methodName> tag. + public const String METHOD_NAME = "methodName"; + /// MethodResponse token + /// Corresponds to the <methodResponse> tag. + public const String METHOD_RESPONSE = "methodResponse"; + /// Fault response token. + /// Corresponds to the <fault> tag. + public const String FAULT = "fault"; + /// FaultCode token. + /// Corresponds to the <faultCode> tag. + public const String FAULT_CODE = "faultCode"; + /// FaultString token. + /// Corresponds to the <faultString> tag. + public const String FAULT_STRING = "faultString"; + } } diff --git a/libsecondlife-cs/libsecondlife.csproj b/libsecondlife-cs/libsecondlife.csproj index 5ae0644e..72e6788d 100644 --- a/libsecondlife-cs/libsecondlife.csproj +++ b/libsecondlife-cs/libsecondlife.csproj @@ -168,6 +168,7 @@ Code + Code @@ -177,6 +178,7 @@ Code + Code @@ -186,6 +188,7 @@ Code + Code @@ -198,6 +201,8 @@ Code + + Code