/* * Copyright (c) 2006-2014, openmetaverse.org * All rights reserved. * * - Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * - Neither the name of the openmetaverse.org nor the names * of its contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.IO; using System.Xml; using System.Xml.Schema; using System.Text; namespace OpenMetaverse.StructuredData { /// /// /// public static partial class OSDParser { private static XmlSchema XmlSchema; private static XmlTextReader XmlTextReader; private static string LastXmlErrors = String.Empty; private static object XmlValidationLock = new object(); /// /// /// /// /// public static OSD DeserializeLLSDXml(byte[] xmlData) { return DeserializeLLSDXml(new XmlTextReader(new MemoryStream(xmlData, false))); } public static OSD DeserializeLLSDXml(Stream xmlStream) { return DeserializeLLSDXml(new XmlTextReader(xmlStream)); } /// /// /// /// /// public static OSD DeserializeLLSDXml(string xmlData) { byte[] bytes = Utils.StringToBytes(xmlData); return DeserializeLLSDXml(new XmlTextReader(new MemoryStream(bytes, false))); } /// /// /// /// /// public static OSD DeserializeLLSDXml(XmlTextReader xmlData) { try { xmlData.Read(); SkipWhitespace(xmlData); xmlData.Read(); OSD ret = ParseLLSDXmlElement(xmlData); return ret; } catch { return new OSD(); } } /// /// /// /// /// public static byte[] SerializeLLSDXmlBytes(OSD data) { return Encoding.UTF8.GetBytes(SerializeLLSDXmlString(data)); } /// /// /// /// /// public static string SerializeLLSDXmlString(OSD data) { StringWriter sw = new StringWriter(); XmlTextWriter writer = new XmlTextWriter(sw); writer.Formatting = Formatting.None; writer.WriteStartElement(String.Empty, "llsd", String.Empty); SerializeLLSDXmlElement(writer, data); writer.WriteEndElement(); writer.Close(); return sw.ToString(); } /// /// /// /// /// public static void SerializeLLSDXmlElement(XmlTextWriter writer, OSD data) { switch (data.Type) { case OSDType.Unknown: writer.WriteStartElement(String.Empty, "undef", String.Empty); writer.WriteEndElement(); break; case OSDType.Boolean: writer.WriteStartElement(String.Empty, "boolean", String.Empty); writer.WriteString(data.AsString()); writer.WriteEndElement(); break; case OSDType.Integer: writer.WriteStartElement(String.Empty, "integer", String.Empty); writer.WriteString(data.AsString()); writer.WriteEndElement(); break; case OSDType.Real: writer.WriteStartElement(String.Empty, "real", String.Empty); writer.WriteString(data.AsString()); writer.WriteEndElement(); break; case OSDType.String: writer.WriteStartElement(String.Empty, "string", String.Empty); writer.WriteString(data.AsString()); writer.WriteEndElement(); break; case OSDType.UUID: writer.WriteStartElement(String.Empty, "uuid", String.Empty); writer.WriteString(data.AsString()); writer.WriteEndElement(); break; case OSDType.Date: writer.WriteStartElement(String.Empty, "date", String.Empty); writer.WriteString(data.AsString()); writer.WriteEndElement(); break; case OSDType.URI: writer.WriteStartElement(String.Empty, "uri", String.Empty); writer.WriteString(data.AsString()); writer.WriteEndElement(); break; case OSDType.Binary: writer.WriteStartElement(String.Empty, "binary", String.Empty); writer.WriteStartAttribute(String.Empty, "encoding", String.Empty); writer.WriteString("base64"); writer.WriteEndAttribute(); writer.WriteString(data.AsString()); writer.WriteEndElement(); break; case OSDType.Map: OSDMap map = (OSDMap)data; writer.WriteStartElement(String.Empty, "map", String.Empty); foreach (KeyValuePair kvp in map) { writer.WriteStartElement(String.Empty, "key", String.Empty); writer.WriteString(kvp.Key); writer.WriteEndElement(); SerializeLLSDXmlElement(writer, kvp.Value); } writer.WriteEndElement(); break; case OSDType.Array: OSDArray array = (OSDArray)data; writer.WriteStartElement(String.Empty, "array", String.Empty); for (int i = 0; i < array.Count; i++) { SerializeLLSDXmlElement(writer, array[i]); } writer.WriteEndElement(); break; } } /// /// /// /// /// /// public static bool TryValidateLLSDXml(XmlTextReader xmlData, out string error) { lock (XmlValidationLock) { LastXmlErrors = String.Empty; XmlTextReader = xmlData; CreateLLSDXmlSchema(); XmlReaderSettings readerSettings = new XmlReaderSettings(); readerSettings.ValidationType = ValidationType.Schema; readerSettings.Schemas.Add(XmlSchema); readerSettings.ValidationEventHandler += new ValidationEventHandler(LLSDXmlSchemaValidationHandler); XmlReader reader = XmlReader.Create(xmlData, readerSettings); try { while (reader.Read()) { } } catch (XmlException) { error = LastXmlErrors; return false; } if (LastXmlErrors == String.Empty) { error = null; return true; } else { error = LastXmlErrors; return false; } } } /// /// /// /// /// private static OSD ParseLLSDXmlElement(XmlTextReader reader) { SkipWhitespace(reader); if (reader.NodeType != XmlNodeType.Element) throw new OSDException("Expected an element"); string type = reader.LocalName; OSD ret; switch (type) { case "undef": if (reader.IsEmptyElement) { reader.Read(); return new OSD(); } reader.Read(); SkipWhitespace(reader); ret = new OSD(); break; case "boolean": if (reader.IsEmptyElement) { reader.Read(); return OSD.FromBoolean(false); } if (reader.Read()) { string s = reader.ReadString().Trim(); if (!String.IsNullOrEmpty(s) && (s == "true" || s == "1")) { ret = OSD.FromBoolean(true); break; } } ret = OSD.FromBoolean(false); break; case "integer": if (reader.IsEmptyElement) { reader.Read(); return OSD.FromInteger(0); } if (reader.Read()) { int value = 0; Int32.TryParse(reader.ReadString().Trim(), out value); ret = OSD.FromInteger(value); break; } ret = OSD.FromInteger(0); break; case "real": if (reader.IsEmptyElement) { reader.Read(); return OSD.FromReal(0d); } if (reader.Read()) { double value = 0d; string str = reader.ReadString().Trim().ToLower(); if (str == "nan") value = Double.NaN; else Utils.TryParseDouble(str, out value); ret = OSD.FromReal(value); break; } ret = OSD.FromReal(0d); break; case "uuid": if (reader.IsEmptyElement) { reader.Read(); return OSD.FromUUID(UUID.Zero); } if (reader.Read()) { UUID value = UUID.Zero; UUID.TryParse(reader.ReadString().Trim(), out value); ret = OSD.FromUUID(value); break; } ret = OSD.FromUUID(UUID.Zero); break; case "date": if (reader.IsEmptyElement) { reader.Read(); return OSD.FromDate(Utils.Epoch); } if (reader.Read()) { DateTime value = Utils.Epoch; DateTime.TryParse(reader.ReadString().Trim(), out value); ret = OSD.FromDate(value); break; } ret = OSD.FromDate(Utils.Epoch); break; case "string": if (reader.IsEmptyElement) { reader.Read(); return OSD.FromString(String.Empty); } if (reader.Read()) { ret = OSD.FromString(reader.ReadString()); break; } ret = OSD.FromString(String.Empty); break; case "binary": if (reader.IsEmptyElement) { reader.Read(); return OSD.FromBinary(Utils.EmptyBytes); } if (reader.GetAttribute("encoding") != null && reader.GetAttribute("encoding") != "base64") throw new OSDException("Unsupported binary encoding: " + reader.GetAttribute("encoding")); if (reader.Read()) { try { ret = OSD.FromBinary(Convert.FromBase64String(reader.ReadString().Trim())); break; } catch (FormatException ex) { throw new OSDException("Binary decoding exception: " + ex.Message); } } ret = OSD.FromBinary(Utils.EmptyBytes); break; case "uri": if (reader.IsEmptyElement) { reader.Read(); return OSD.FromUri(new Uri(String.Empty, UriKind.RelativeOrAbsolute)); } if (reader.Read()) { ret = OSD.FromUri(new Uri(reader.ReadString(), UriKind.RelativeOrAbsolute)); break; } ret = OSD.FromUri(new Uri(String.Empty, UriKind.RelativeOrAbsolute)); break; case "map": return ParseLLSDXmlMap(reader); case "array": return ParseLLSDXmlArray(reader); default: reader.Read(); ret = null; break; } if (reader.NodeType != XmlNodeType.EndElement || reader.LocalName != type) { throw new OSDException("Expected "); } else { reader.Read(); return ret; } } private static OSDMap ParseLLSDXmlMap(XmlTextReader reader) { if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "map") throw new NotImplementedException("Expected "); OSDMap map = new OSDMap(); if (reader.IsEmptyElement) { reader.Read(); return map; } if (reader.Read()) { while (true) { SkipWhitespace(reader); if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "map") { reader.Read(); break; } if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "key") throw new OSDException("Expected "); string key = reader.ReadString(); if (reader.NodeType != XmlNodeType.EndElement || reader.LocalName != "key") throw new OSDException("Expected "); if (reader.Read()) map[key] = ParseLLSDXmlElement(reader); else throw new OSDException("Failed to parse a value for key " + key); } } return map; } private static OSDArray ParseLLSDXmlArray(XmlTextReader reader) { if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "array") throw new OSDException("Expected "); OSDArray array = new OSDArray(); if (reader.IsEmptyElement) { reader.Read(); return array; } if (reader.Read()) { while (true) { SkipWhitespace(reader); if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "array") { reader.Read(); break; } array.Add(ParseLLSDXmlElement(reader)); } } return array; } private static void SkipWhitespace(XmlTextReader reader) { while ( reader.NodeType == XmlNodeType.Comment || reader.NodeType == XmlNodeType.Whitespace || reader.NodeType == XmlNodeType.SignificantWhitespace || reader.NodeType == XmlNodeType.XmlDeclaration) { reader.Read(); } } private static void CreateLLSDXmlSchema() { if (XmlSchema == null) { #region XSD string schemaText = @" "; #endregion XSD MemoryStream stream = new MemoryStream(Encoding.ASCII.GetBytes(schemaText)); XmlSchema = new XmlSchema(); XmlSchema = XmlSchema.Read(stream, new ValidationEventHandler(LLSDXmlSchemaValidationHandler)); } } private static void LLSDXmlSchemaValidationHandler(object sender, ValidationEventArgs args) { string error = String.Format("Line: {0} - Position: {1} - {2}", XmlTextReader.LineNumber, XmlTextReader.LinePosition, args.Message); if (LastXmlErrors == String.Empty) LastXmlErrors = error; else LastXmlErrors += Environment.NewLine + error; } } }