From a71f4c021b8d2fcf0e121145428aa797b8f7ca19 Mon Sep 17 00:00:00 2001 From: MW Date: Fri, 7 Dec 2007 22:29:16 +0000 Subject: [PATCH] "Borrowed" the LLSD class from a older version of libsl, so that our LLSD decoding (used by CAPS) works again. --- .../Communications/Capabilities/Caps.cs | 10 +- .../Communications/Capabilities/LLSD.cs | 648 ++++++++++++++++++ .../Capabilities/LLSDCapsDetails.cs | 3 +- .../Capabilities/LLSDHelpers.cs | 20 +- .../Capabilities/LLSDStreamHandler.cs | 6 +- 5 files changed, 672 insertions(+), 15 deletions(-) create mode 100644 OpenSim/Framework/Communications/Capabilities/LLSD.cs diff --git a/OpenSim/Framework/Communications/Capabilities/Caps.cs b/OpenSim/Framework/Communications/Capabilities/Caps.cs index f7cb20c16d..dd30e9f28f 100644 --- a/OpenSim/Framework/Communications/Capabilities/Caps.cs +++ b/OpenSim/Framework/Communications/Capabilities/Caps.cs @@ -55,7 +55,7 @@ namespace OpenSim.Region.Capabilities private string m_requestPath = "0000/"; private string m_mapLayerPath = "0001/"; private string m_newInventory = "0002/"; - //private string m_requestTexture = "0003/"; + //private string m_requestTexture = "0003/"; private string m_notecardUpdatePath = "0004/"; //private string eventQueue = "0100/"; private BaseHttpServer m_httpListener; @@ -125,7 +125,7 @@ namespace OpenSim.Region.Capabilities /// public string CapsRequest(string request, string path, string param) { - // Console.WriteLine("caps request " + request); + //Console.WriteLine("caps request " + request); string result = LLSDHelpers.SerialiseLLSDReply(GetCapabilities()); return result; } @@ -140,10 +140,11 @@ namespace OpenSim.Region.Capabilities string capsBaseUrl = "http://" + m_httpListenerHostName + ":" + m_httpListenPort.ToString() + "/CAPS/" + m_capsObjectPath; caps.MapLayer = capsBaseUrl + m_mapLayerPath; - // caps.RequestTextureDownload = capsBaseUrl + m_requestTexture; + // caps.RequestTextureDownload = capsBaseUrl + m_requestTexture; caps.NewFileAgentInventory = capsBaseUrl + m_newInventory; caps.UpdateNotecardAgentInventory = capsBaseUrl + m_notecardUpdatePath; caps.UpdateScriptAgentInventory = capsBaseUrl + m_notecardUpdatePath; + // caps.UpdateScriptTaskInventory = capsBaseUrl + m_requestTexture; return caps; } @@ -258,7 +259,8 @@ namespace OpenSim.Region.Capabilities /// public string NoteCardAgentInventory(string request, string path, string param) { - libsecondlife.StructuredData.LLSDMap hash = (libsecondlife.StructuredData.LLSDMap)libsecondlife.StructuredData.LLSDParser.DeserializeBinary(Helpers.StringToField(request)); + //libsecondlife.StructuredData.LLSDMap hash = (libsecondlife.StructuredData.LLSDMap)libsecondlife.StructuredData.LLSDParser.DeserializeBinary(Helpers.StringToField(request)); + Hashtable hash = (Hashtable)LLSD.LLSDDeserialize(Helpers.StringToField(request)); LLSDItemUpdate llsdRequest = new LLSDItemUpdate(); LLSDHelpers.DeserialiseLLSDMap(hash, llsdRequest); diff --git a/OpenSim/Framework/Communications/Capabilities/LLSD.cs b/OpenSim/Framework/Communications/Capabilities/LLSD.cs new file mode 100644 index 0000000000..60b5f756b4 --- /dev/null +++ b/OpenSim/Framework/Communications/Capabilities/LLSD.cs @@ -0,0 +1,648 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Xml; +using System.IO; +using libsecondlife; +using System.Security.Cryptography; +using System.Text; + +namespace OpenSim.Region.Capabilities +{ + /// + /// + /// + public static class LLSD + { + /// + /// + /// + public class LLSDParseException : Exception + { + public LLSDParseException(string message) : base(message) { } + } + + /// + /// + /// + public class LLSDSerializeException : Exception + { + public LLSDSerializeException(string message) : base(message) { } + } + + /// + /// + /// + /// + /// + public static object LLSDDeserialize(byte[] b) + { + return LLSDDeserialize(new MemoryStream(b, false)); + } + + /// + /// + /// + /// + /// + public static object LLSDDeserialize(Stream st) + { + XmlTextReader reader = new XmlTextReader(st); + reader.Read(); + SkipWS(reader); + + if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "llsd") + throw new LLSDParseException("Expected "); + + reader.Read(); + object ret = LLSDParseOne(reader); + SkipWS(reader); + + if (reader.NodeType != XmlNodeType.EndElement || reader.LocalName != "llsd") + throw new LLSDParseException("Expected "); + + return ret; + } + + /// + /// + /// + /// + /// + public static byte[] LLSDSerialize(object obj) + { + StringWriter sw = new StringWriter(); + XmlTextWriter writer = new XmlTextWriter(sw); + writer.Formatting = Formatting.None; + + writer.WriteStartElement(String.Empty, "llsd", String.Empty); + LLSDWriteOne(writer, obj); + writer.WriteEndElement(); + + writer.Close(); + + return Encoding.UTF8.GetBytes(sw.ToString()); + } + + /// + /// + /// + /// + /// + public static void LLSDWriteOne(XmlTextWriter writer, object obj) + { + if (obj == null) + { + writer.WriteStartElement(String.Empty, "undef", String.Empty); + writer.WriteEndElement(); + return; + } + + if (obj is string) + { + writer.WriteStartElement(String.Empty, "string", String.Empty); + writer.WriteString((string)obj); + writer.WriteEndElement(); + } + else if (obj is int) + { + writer.WriteStartElement(String.Empty, "integer", String.Empty); + writer.WriteString(obj.ToString()); + writer.WriteEndElement(); + } + else if (obj is double) + { + writer.WriteStartElement(String.Empty, "real", String.Empty); + writer.WriteString(obj.ToString()); + writer.WriteEndElement(); + } + else if (obj is bool) + { + bool b = (bool)obj; + writer.WriteStartElement(String.Empty, "boolean", String.Empty); + writer.WriteString(b ? "1" : "0"); + writer.WriteEndElement(); + } + else if (obj is ulong) + { + throw new Exception("ulong in LLSD is currently not implemented, fix me!"); + } + else if (obj is LLUUID) + { + LLUUID u = (LLUUID)obj; + writer.WriteStartElement(String.Empty, "uuid", String.Empty); + writer.WriteString(u.ToStringHyphenated()); + writer.WriteEndElement(); + } + else if (obj is Hashtable) + { + Hashtable h = obj as Hashtable; + writer.WriteStartElement(String.Empty, "map", String.Empty); + foreach (string key in h.Keys) + { + writer.WriteStartElement(String.Empty, "key", String.Empty); + writer.WriteString(key); + writer.WriteEndElement(); + LLSDWriteOne(writer, h[key]); + } + writer.WriteEndElement(); + } + else if (obj is ArrayList) + { + ArrayList a = obj as ArrayList; + writer.WriteStartElement(String.Empty, "array", String.Empty); + foreach (object item in a) + { + LLSDWriteOne(writer, item); + } + writer.WriteEndElement(); + } + else if (obj is byte[]) + { + byte[] b = obj as byte[]; + writer.WriteStartElement(String.Empty, "binary", String.Empty); + + writer.WriteStartAttribute(String.Empty, "encoding", String.Empty); + writer.WriteString("base64"); + writer.WriteEndAttribute(); + + //// Calculate the length of the base64 output + //long length = (long)(4.0d * b.Length / 3.0d); + //if (length % 4 != 0) length += 4 - (length % 4); + + //// Create the char[] for base64 output and fill it + //char[] tmp = new char[length]; + //int i = Convert.ToBase64CharArray(b, 0, b.Length, tmp, 0); + + //writer.WriteString(new String(tmp)); + + writer.WriteString(Convert.ToBase64String(b)); + writer.WriteEndElement(); + } + else + { + throw new LLSDSerializeException("Unknown type " + obj.GetType().Name); + } + } + + /// + /// + /// + /// + /// + public static object LLSDParseOne(XmlTextReader reader) + { + SkipWS(reader); + if (reader.NodeType != XmlNodeType.Element) + throw new LLSDParseException("Expected an element"); + + string dtype = reader.LocalName; + object ret = null; + + switch (dtype) + { + case "undef": + { + if (reader.IsEmptyElement) + { + reader.Read(); + return null; + } + + reader.Read(); + SkipWS(reader); + ret = null; + break; + } + case "boolean": + { + if (reader.IsEmptyElement) + { + reader.Read(); + return false; + } + + reader.Read(); + string s = reader.ReadString().Trim(); + + if (s == String.Empty || s == "false" || s == "0") + ret = false; + else if (s == "true" || s == "1") + ret = true; + else + throw new LLSDParseException("Bad boolean value " + s); + + break; + } + case "integer": + { + if (reader.IsEmptyElement) + { + reader.Read(); + return 0; + } + + reader.Read(); + ret = Convert.ToInt32(reader.ReadString().Trim()); + break; + } + case "real": + { + if (reader.IsEmptyElement) + { + reader.Read(); + return 0.0f; + } + + reader.Read(); + ret = Convert.ToDouble(reader.ReadString().Trim()); + break; + } + case "uuid": + { + if (reader.IsEmptyElement) + { + reader.Read(); + return LLUUID.Zero; + } + + reader.Read(); + ret = new LLUUID(reader.ReadString().Trim()); + break; + } + case "string": + { + if (reader.IsEmptyElement) + { + reader.Read(); + return String.Empty; + } + + reader.Read(); + ret = reader.ReadString(); + break; + } + case "binary": + { + if (reader.IsEmptyElement) + { + reader.Read(); + return new byte[0]; + } + + if (reader.GetAttribute("encoding") != null && + reader.GetAttribute("encoding") != "base64") + { + throw new LLSDParseException("Unknown encoding: " + reader.GetAttribute("encoding")); + } + + reader.Read(); + FromBase64Transform b64 = new FromBase64Transform(FromBase64TransformMode.IgnoreWhiteSpaces); + byte[] inp = Encoding.ASCII.GetBytes(reader.ReadString()); + ret = b64.TransformFinalBlock(inp, 0, inp.Length); + break; + } + case "date": + { + reader.Read(); + throw new Exception("LLSD TODO: date"); + } + case "map": + { + return LLSDParseMap(reader); + } + case "array": + { + return LLSDParseArray(reader); + } + default: + throw new LLSDParseException("Unknown element <" + dtype + ">"); + } + + if (reader.NodeType != XmlNodeType.EndElement || reader.LocalName != dtype) + { + throw new LLSDParseException("Expected "); + } + + reader.Read(); + return ret; + } + + /// + /// + /// + /// + /// + public static Hashtable LLSDParseMap(XmlTextReader reader) + { + Hashtable ret = new Hashtable(); + + if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "map") + throw new LLSDParseException("Expected "); + + if (reader.IsEmptyElement) + { + reader.Read(); + return ret; + } + + reader.Read(); + + while (true) + { + SkipWS(reader); + if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "map") + { + reader.Read(); + break; + } + + if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "key") + throw new LLSDParseException("Expected "); + + string key = reader.ReadString(); + + if (reader.NodeType != XmlNodeType.EndElement || reader.LocalName != "key") + throw new LLSDParseException("Expected "); + + reader.Read(); + object val = LLSDParseOne(reader); + ret[key] = val; + } + + return ret; // TODO + } + + /// + /// + /// + /// + /// + public static ArrayList LLSDParseArray(XmlTextReader reader) + { + ArrayList ret = new ArrayList(); + + if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "array") + throw new LLSDParseException("Expected "); + + if (reader.IsEmptyElement) + { + reader.Read(); + return ret; + } + + reader.Read(); + + while (true) + { + SkipWS(reader); + + if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "array") + { + reader.Read(); + break; + } + + ret.Insert(ret.Count, LLSDParseOne(reader)); + } + + return ret; // TODO + } + + /// + /// + /// + /// + /// + private static string GetSpaces(int count) + { + StringBuilder b = new StringBuilder(); + for (int i = 0; i < count; i++) b.Append(" "); + return b.ToString(); + } + + /// + /// + /// + /// + /// + /// + public static String LLSDDump(object obj, int indent) + { + if (obj == null) + { + return GetSpaces(indent) + "- undef\n"; + } + else if (obj is string) + { + return GetSpaces(indent) + "- string \"" + (string)obj + "\"\n"; + } + else if (obj is int) + { + return GetSpaces(indent) + "- integer " + obj.ToString() + "\n"; + } + else if (obj is double) + { + return GetSpaces(indent) + "- float " + obj.ToString() + "\n"; + } + else if (obj is LLUUID) + { + return GetSpaces(indent) + "- uuid " + ((LLUUID)obj).ToStringHyphenated() + Environment.NewLine; + } + else if (obj is Hashtable) + { + StringBuilder ret = new StringBuilder(); + ret.Append(GetSpaces(indent) + "- map" + Environment.NewLine); + Hashtable map = (Hashtable)obj; + + foreach (string key in map.Keys) + { + ret.Append(GetSpaces(indent + 2) + "- key \"" + key + "\"" + Environment.NewLine); + ret.Append(LLSDDump(map[key], indent + 3)); + } + + return ret.ToString(); + } + else if (obj is ArrayList) + { + StringBuilder ret = new StringBuilder(); + ret.Append(GetSpaces(indent) + "- array\n"); + ArrayList list = (ArrayList)obj; + + foreach (object item in list) + { + ret.Append(LLSDDump(item, indent + 2)); + } + + return ret.ToString(); + } + else if (obj is byte[]) + { + return GetSpaces(indent) + "- binary\n" + Helpers.FieldToHexString((byte[])obj, GetSpaces(indent)) + + Environment.NewLine; + } + else + { + return GetSpaces(indent) + "- unknown type " + obj.GetType().Name + Environment.NewLine; + } + } + + public static object ParseTerseLLSD(string llsd) + { + int notused; + return ParseTerseLLSD(llsd, out notused); + } + + public static object ParseTerseLLSD(string llsd, out int endPos) + { + if (llsd.Length == 0) + { + endPos = 0; + return null; + } + + // Identify what type of object this is + switch (llsd[0]) + { + case '!': + throw new LLSDParseException("Undefined value type encountered"); + case '1': + endPos = 1; + return true; + case '0': + endPos = 1; + return false; + case 'i': + { + if (llsd.Length < 2) throw new LLSDParseException("Integer value type with no value"); + int value; + endPos = FindEnd(llsd, 1); + + if (Int32.TryParse(llsd.Substring(1, endPos - 1), out value)) + return value; + else + throw new LLSDParseException("Failed to parse integer value type"); + } + case 'r': + { + if (llsd.Length < 2) throw new LLSDParseException("Real value type with no value"); + double value; + endPos = FindEnd(llsd, 1); + + if (Double.TryParse(llsd.Substring(1, endPos - 1), System.Globalization.NumberStyles.Float, + Helpers.EnUsCulture.NumberFormat, out value)) + return value; + else + throw new LLSDParseException("Failed to parse double value type"); + } + case 'u': + { + if (llsd.Length < 17) throw new LLSDParseException("LLUUID value type with no value"); + LLUUID value; + endPos = FindEnd(llsd, 1); + + if (LLUUID.TryParse(llsd.Substring(1, endPos - 1), out value)) + return value; + else + throw new LLSDParseException("Failed to parse LLUUID value type"); + } + case 'b': + //byte[] value = new byte[llsd.Length - 1]; + // This isn't the actual binary LLSD format, just the terse format sent + // at login so I don't even know if there is a binary type + throw new LLSDParseException("Binary value type is unimplemented"); + case 's': + case 'l': + if (llsd.Length < 2) throw new LLSDParseException("String value type with no value"); + endPos = FindEnd(llsd, 1); + return llsd.Substring(1, endPos - 1); + case 'd': + // Never seen one before, don't know what the format is + throw new LLSDParseException("Date value type is unimplemented"); + case '[': + { + if (llsd.IndexOf(']') == -1) throw new LLSDParseException("Invalid array"); + + int pos = 0; + ArrayList array = new ArrayList(); + + while (llsd[pos] != ']') + { + ++pos; + + // Advance past comma if need be + if (llsd[pos] == ',') ++pos; + + // Allow a single whitespace character + if (pos < llsd.Length && llsd[pos] == ' ') ++pos; + + int end; + array.Add(ParseTerseLLSD(llsd.Substring(pos), out end)); + pos += end; + } + + endPos = pos + 1; + return array; + } + case '{': + { + if (llsd.IndexOf('}') == -1) throw new LLSDParseException("Invalid map"); + + int pos = 0; + Hashtable hashtable = new Hashtable(); + + while (llsd[pos] != '}') + { + ++pos; + + // Advance past comma if need be + if (llsd[pos] == ',') ++pos; + + // Allow a single whitespace character + if (pos < llsd.Length && llsd[pos] == ' ') ++pos; + + if (llsd[pos] != '\'') throw new LLSDParseException("Expected a map key"); + int endquote = llsd.IndexOf('\'', pos + 1); + if (endquote == -1 || (endquote + 1) >= llsd.Length || llsd[endquote + 1] != ':') + throw new LLSDParseException("Invalid map format"); + string key = llsd.Substring(pos, endquote - pos); + key = key.Replace("'", String.Empty); + pos += (endquote - pos) + 2; + + int end; + hashtable.Add(key, ParseTerseLLSD(llsd.Substring(pos), out end)); + pos += end; + } + + endPos = pos + 1; + return hashtable; + } + default: + throw new Exception("Unknown value type"); + } + } + + private static int FindEnd(string llsd, int start) + { + int end = llsd.IndexOfAny(new char[] { ',', ']', '}' }); + if (end == -1) end = llsd.Length - 1; + return end; + } + + /// + /// + /// + /// + private static void SkipWS(XmlTextReader reader) + { + while ( + reader.NodeType == XmlNodeType.Comment || + reader.NodeType == XmlNodeType.Whitespace || + reader.NodeType == XmlNodeType.SignificantWhitespace || + reader.NodeType == XmlNodeType.XmlDeclaration) + { + reader.Read(); + } + } + } +} diff --git a/OpenSim/Framework/Communications/Capabilities/LLSDCapsDetails.cs b/OpenSim/Framework/Communications/Capabilities/LLSDCapsDetails.cs index 35caa15b29..c50d0a940a 100644 --- a/OpenSim/Framework/Communications/Capabilities/LLSDCapsDetails.cs +++ b/OpenSim/Framework/Communications/Capabilities/LLSDCapsDetails.cs @@ -34,10 +34,11 @@ namespace OpenSim.Region.Capabilities public string MapLayer = ""; public string NewFileAgentInventory = ""; //public string EventQueueGet = ""; - // public string RequestTextureDownload = ""; + // public string RequestTextureDownload = ""; // public string ChatSessionRequest = ""; public string UpdateNotecardAgentInventory = ""; public string UpdateScriptAgentInventory = ""; + // public string UpdateScriptTaskInventory = ""; // public string ParcelVoiceInfoRequest = ""; public LLSDCapsDetails() diff --git a/OpenSim/Framework/Communications/Capabilities/LLSDHelpers.cs b/OpenSim/Framework/Communications/Capabilities/LLSDHelpers.cs index 793c3668dd..99a8f35087 100644 --- a/OpenSim/Framework/Communications/Capabilities/LLSDHelpers.cs +++ b/OpenSim/Framework/Communications/Capabilities/LLSDHelpers.cs @@ -76,8 +76,9 @@ namespace OpenSim.Region.Capabilities writer.WriteStartElement(String.Empty, "key", String.Empty); writer.WriteString(fields[i].Name); writer.WriteEndElement(); - libsecondlife.StructuredData.LLSDParser.SerializeXmlElement( - writer, libsecondlife.StructuredData.LLSD.FromObject(fieldValue)); + LLSD.LLSDWriteOne(writer, fieldValue); + // libsecondlife.StructuredData.LLSDParser.SerializeXmlElement( + // writer, libsecondlife.StructuredData.LLSD.FromObject(fieldValue)); } } writer.WriteEndElement(); @@ -100,12 +101,13 @@ namespace OpenSim.Region.Capabilities } else { - libsecondlife.StructuredData.LLSDParser.SerializeXmlElement( - writer, libsecondlife.StructuredData.LLSD.FromObject(obj)); + LLSD.LLSDWriteOne(writer, obj); + //libsecondlife.StructuredData.LLSDParser.SerializeXmlElement( + // writer, libsecondlife.StructuredData.LLSD.FromObject(obj)); } } - - public static object DeserialiseLLSDMap(libsecondlife.StructuredData.LLSDMap llsd, object obj) + + public static object DeserialiseLLSDMap(Hashtable llsd, object obj) { Type myType = obj.GetType(); LLSDType[] llsdattributes = (LLSDType[]) myType.GetCustomAttributes(typeof (LLSDType), false); @@ -120,10 +122,12 @@ namespace OpenSim.Region.Capabilities FieldInfo field = myType.GetField((string) enumerator.Key); if (field != null) { - if (enumerator.Value is libsecondlife.StructuredData.LLSDMap) + // if (enumerator.Value is libsecondlife.StructuredData.LLSDMap) + if (enumerator.Value is Hashtable) { object fieldValue = field.GetValue(obj); - DeserialiseLLSDMap((libsecondlife.StructuredData.LLSDMap) enumerator.Value, fieldValue); + DeserialiseLLSDMap((Hashtable)enumerator.Value, fieldValue); + // DeserialiseLLSDMap((libsecondlife.StructuredData.LLSDMap) enumerator.Value, fieldValue); } else if (enumerator.Value is ArrayList) { diff --git a/OpenSim/Framework/Communications/Capabilities/LLSDStreamHandler.cs b/OpenSim/Framework/Communications/Capabilities/LLSDStreamHandler.cs index ed25e71997..71cc3580ea 100644 --- a/OpenSim/Framework/Communications/Capabilities/LLSDStreamHandler.cs +++ b/OpenSim/Framework/Communications/Capabilities/LLSDStreamHandler.cs @@ -54,8 +54,10 @@ namespace OpenSim.Region.Capabilities //string requestBody = streamReader.ReadToEnd(); //streamReader.Close(); - libsecondlife.StructuredData.LLSDMap hash = (libsecondlife.StructuredData.LLSDMap) - libsecondlife.StructuredData.LLSDParser.DeserializeXml(new XmlTextReader(request)); + // libsecondlife.StructuredData.LLSDMap hash = (libsecondlife.StructuredData.LLSDMap) + // libsecondlife.StructuredData.LLSDParser.DeserializeXml(new XmlTextReader(request)); + + Hashtable hash = (Hashtable)LLSD.LLSDDeserialize(request); TRequest llsdRequest = new TRequest(); LLSDHelpers.DeserialiseLLSDMap(hash, llsdRequest);