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 libsecondlife { /// /// /// 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 = obj as LLUUID; 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; } } /// /// /// /// 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(); } } } }