/* * Copyright (c) 2007-2008, Second Life Reverse Engineering Team * 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 Second Life Reverse Engineering Team 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.Text; using System.IO; namespace libsecondlife.StructuredData { public static partial class LLSDParser { private const string notationHead = "\n"; private const string baseIntend = " "; private const char undefNotationValue = '!'; private const char trueNotationValueOne = '1'; private const char trueNotationValueTwo = 't'; private static char[] trueNotationValueTwoFull = { 't', 'r', 'u', 'e' }; private const char trueNotationValueThree = 'T'; private static char[] trueNotationValueThreeFull = { 'T', 'R', 'U', 'E' }; private const char falseNotationValueOne = '0'; private const char falseNotationValueTwo = 'f'; private static char[] falseNotationValueTwoFull = { 'f', 'a', 'l', 's', 'e' }; private const char falseNotationValueThree = 'F'; private static char[] falseNotationValueThreeFull = { 'F', 'A', 'L', 'S', 'E' }; private const char integerNotationMarker = 'i'; private const char realNotationMarker = 'r'; private const char uuidNotationMarker = 'u'; private const char binaryNotationMarker = 'b'; private const char stringNotationMarker = 's'; private const char uriNotationMarker = 'l'; private const char dateNotationMarker = 'd'; private const char arrayBeginNotationMarker = '['; private const char arrayEndNotationMarker = ']'; private const char mapBeginNotationMarker = '{'; private const char mapEndNotationMarker = '}'; private const char kommaNotationDelimiter = ','; private const char keyNotationDelimiter = ':'; private const char sizeBeginNotationMarker = '('; private const char sizeEndNotationMarker = ')'; private const char doubleQuotesNotationMarker = '"'; private const char singleQuotesNotationMarker = '\''; public static LLSD DeserializeNotation(string notationData) { StringReader reader = new StringReader( notationData ); LLSD llsd = DeserializeNotation( reader ); reader.Close(); return llsd; } public static LLSD DeserializeNotation( StringReader reader ) { LLSD llsd = DeserializeNotationElement( reader ); return llsd; } public static string SerializeNotation(LLSD llsd) { StringWriter writer = SerializeNotationStream( llsd ); string s = writer.ToString(); writer.Close(); return s; } public static StringWriter SerializeNotationStream(LLSD llsd) { StringWriter writer = new StringWriter(); SerializeNotationElement( writer, llsd ); return writer; } public static string SerializeNotationFormatted(LLSD llsd) { StringWriter writer = SerializeNotationStreamFormatted(llsd); string s = writer.ToString(); writer.Close(); return s; } public static StringWriter SerializeNotationStreamFormatted(LLSD llsd) { StringWriter writer = new StringWriter(); string intend = ""; SerializeNotationElementFormatted( writer, intend, llsd ); return writer; } private static LLSD DeserializeNotationElement( StringReader reader ) { int character = ReadAndSkipWhitespace( reader ); if ( character < 0 ) throw new LLSDException( "Notation LLSD parsing:Unexpected end of stream." ); LLSD llsd; int matching; switch( (char)character) { case undefNotationValue: llsd = new LLSD(); break; case trueNotationValueOne: llsd = LLSD.FromBoolean( true ); break; case trueNotationValueTwo: matching = BufferCharactersEqual( reader, trueNotationValueTwoFull, 1 ); if ( matching > 1 && matching < trueNotationValueTwoFull.Length ) throw new LLSDException( "Notation LLSD parsing: True value parsing error:" ); llsd = LLSD.FromBoolean( true ); break; case trueNotationValueThree: matching = BufferCharactersEqual( reader, trueNotationValueThreeFull, 1 ); if ( matching > 1 && matching < trueNotationValueThreeFull.Length ) throw new LLSDException( "Notation LLSD parsing: True value parsing error:" ); llsd = LLSD.FromBoolean( true ); break; case falseNotationValueOne: llsd = LLSD.FromBoolean( false ); break; case falseNotationValueTwo: matching = BufferCharactersEqual( reader, falseNotationValueTwoFull, 1 ); if ( matching > 1 && matching < falseNotationValueTwoFull.Length ) throw new LLSDException( "Notation LLSD parsing: True value parsing error:" ); llsd = LLSD.FromBoolean( false ); break; case falseNotationValueThree: matching = BufferCharactersEqual( reader, falseNotationValueThreeFull, 1 ); if ( matching > 1 && matching < falseNotationValueThreeFull.Length ) throw new LLSDException( "Notation LLSD parsing: True value parsing error:" ); llsd = LLSD.FromBoolean( false ); break; case integerNotationMarker: llsd = DeserializeNotationInteger( reader ); break; case realNotationMarker: llsd = DeserializeNotationReal( reader ); break; case uuidNotationMarker: char[] uuidBuf = new char[36]; if ( reader.Read( uuidBuf, 0, 36 ) < 36) throw new LLSDException( "Notation LLSD parsing: Unexpected end of stream in UUID." ); LLUUID lluuid; if ( !LLUUID.TryParse( new String( uuidBuf ), out lluuid )) throw new LLSDException( "Notation LLSD parsing: Invalid UUID discovered." ); llsd = LLSD.FromUUID( lluuid ); break; case binaryNotationMarker: byte[] bytes = new byte[0]; int bChar = reader.Peek(); if ( bChar < 0 ) throw new LLSDException( "Notation LLSD parsing: Unexpected end of stream in binary." ); if ((char)bChar == sizeBeginNotationMarker ) { throw new LLSDException( "Notation LLSD parsing: Raw binary encoding not supported." ); } else if (Char.IsDigit((char)bChar)) { char[] charsBaseEncoding = new char[2]; if (reader.Read( charsBaseEncoding, 0, 2 ) < 2 ) throw new LLSDException( "Notation LLSD parsing: Unexpected end of stream in binary." ); int baseEncoding; if (!Int32.TryParse( new String( charsBaseEncoding ), out baseEncoding )) throw new LLSDException( "Notation LLSD parsing: Invalid binary encoding base." ); if ( baseEncoding == 64 ) { if (reader.Read() < 0 ) throw new LLSDException( "Notation LLSD parsing: Unexpected end of stream in binary." ); string bytes64 = GetStringDelimitedBy( reader, doubleQuotesNotationMarker ); bytes = Convert.FromBase64String( bytes64 ); } else { throw new LLSDException( "Notation LLSD parsing: Encoding base" + baseEncoding + " + not supported." ); } } llsd = LLSD.FromBinary( bytes ); break; case stringNotationMarker: int numChars = GetLengthInBrackets( reader ); if ( reader.Read() < 0 ) throw new LLSDException( "Notation LLSD parsing: Unexpected end of stream in string." ); char[] chars = new char[numChars]; if ( reader.Read( chars, 0, numChars ) < numChars ) throw new LLSDException( "Notation LLSD parsing: Unexpected end of stream in string." ); if ( reader.Read() < 0 ) throw new LLSDException( "Notation LLSD parsing: Unexpected end of stream in string." ); llsd = LLSD.FromString( new String( chars )); break; case singleQuotesNotationMarker: string sOne = GetStringDelimitedBy( reader, singleQuotesNotationMarker ); llsd = LLSD.FromString( sOne ); break; case doubleQuotesNotationMarker: string sTwo = GetStringDelimitedBy( reader, doubleQuotesNotationMarker ); llsd = LLSD.FromString( sTwo ); break; case uriNotationMarker: if ( reader.Read() < 0 ) throw new LLSDException( "Notation LLSD parsing: Unexpected end of stream in string." ); string sUri = GetStringDelimitedBy( reader, doubleQuotesNotationMarker ); Uri uri; try { uri = new Uri( sUri, UriKind.RelativeOrAbsolute ); } catch { throw new LLSDException( "Notation LLSD parsing: Invalid Uri format detected." ); } llsd = LLSD.FromUri( uri); break; case dateNotationMarker: if (reader.Read() < 0 ) throw new LLSDException( "Notation LLSD parsing: Unexpected end of stream in date." ); string date = GetStringDelimitedBy( reader, doubleQuotesNotationMarker ); DateTime dt; if ( !Helpers.TryParse( date, out dt )) throw new LLSDException( "Notation LLSD parsing: Invalid date discovered." ); llsd = LLSD.FromDate( dt ); break; case arrayBeginNotationMarker: llsd = DeserializeNotationArray( reader ); break; case mapBeginNotationMarker: llsd = DeserializeNotationMap( reader ); break; default: throw new LLSDException( "Notation LLSD parsing: Unknown type marker '" + (char)character + "'." ); } return llsd; } private static LLSD DeserializeNotationInteger( StringReader reader ) { int character; StringBuilder s = new StringBuilder(); if ((( character = reader.Peek()) > 0 ) && ((char)character == '-' )) { s.Append((char)character); reader.Read(); } while (( character = reader.Peek()) > 0 && Char.IsDigit((char)character)) { s.Append((char)character); reader.Read(); } int integer; if ( !Helpers.TryParse( s.ToString(), out integer )) throw new LLSDException( "Notation LLSD parsing: Can't parse integer value." ); return LLSD.FromInteger( integer ); } private static LLSD DeserializeNotationReal( StringReader reader ) { int character; StringBuilder s = new StringBuilder(); if ((( character = reader.Peek()) > 0 ) && ((char)character == '-' )) { s.Append((char)character); reader.Read(); } while ((( character = reader.Peek()) > 0) && ( Char.IsDigit((char)character) || (char)character == '.' || (char)character == 'e' || (char)character == 'E' || (char)character == '+' )) { s.Append((char)character); reader.Read(); } double dbl; if (!Helpers.TryParse(s.ToString(), out dbl)) throw new LLSDException("Notation LLSD parsing: Can't parse real value: " + s.ToString()); return LLSD.FromReal( dbl ); } private static LLSD DeserializeNotationArray( StringReader reader ) { int character; LLSDArray llsdArray = new LLSDArray(); while((( character = PeekAndSkipWhitespace( reader )) > 0) && ((char)character != arrayEndNotationMarker)) { llsdArray.Add( DeserializeNotationElement( reader )); character = ReadAndSkipWhitespace( reader ); if (character < 0 ) throw new LLSDException( "Notation LLSD parsing: Unexpected end of array discovered." ); else if ((char)character == kommaNotationDelimiter ) continue; else if ((char)character == arrayEndNotationMarker ) break; } if ( character < 0 ) throw new LLSDException( "Notation LLSD parsing: Unexpected end of array discovered." ); return (LLSD)llsdArray; } private static LLSD DeserializeNotationMap( StringReader reader ) { int character; LLSDMap llsdMap = new LLSDMap(); while((( character = PeekAndSkipWhitespace( reader )) > 0 ) && ((char)character != mapEndNotationMarker )) { LLSD llsdKey = DeserializeNotationElement( reader ); if ( llsdKey.Type != LLSDType.String ) throw new LLSDException( "Notation LLSD parsing: Invalid key in map" ); string key = llsdKey.AsString(); character = ReadAndSkipWhitespace( reader ); if ((char)character != keyNotationDelimiter ) throw new LLSDException( "Notation LLSD parsing: Unexpected end of stream in map." ); if ((char)character != keyNotationDelimiter ) throw new LLSDException( "Notation LLSD parsing: Invalid delimiter in map." ); llsdMap[key] = DeserializeNotationElement( reader ); character = ReadAndSkipWhitespace( reader ); if ( character < 0 ) throw new LLSDException( "Notation LLSD parsing: Unexpected end of map discovered." ); else if ((char)character == kommaNotationDelimiter ) continue; else if ((char)character == mapEndNotationMarker ) break; } if ( character < 0 ) throw new LLSDException( "Notation LLSD parsing: Unexpected end of map discovered." ); return (LLSD)llsdMap; } private static void SerializeNotationElement( StringWriter writer, LLSD llsd ) { switch( llsd.Type ) { case LLSDType.Unknown: writer.Write( undefNotationValue ); break; case LLSDType.Boolean: if ( llsd.AsBoolean() ) writer.Write( trueNotationValueTwo ); else writer.Write( falseNotationValueTwo ); break; case LLSDType.Integer: writer.Write( integerNotationMarker ); writer.Write( llsd.AsString()); break; case LLSDType.Real: writer.Write( realNotationMarker ); writer.Write( llsd.AsString()); break; case LLSDType.UUID: writer.Write( uuidNotationMarker ); writer.Write( llsd.AsString()); break; case LLSDType.String: writer.Write( singleQuotesNotationMarker ); writer.Write( EscapeCharacter( llsd.AsString(), singleQuotesNotationMarker )); writer.Write( singleQuotesNotationMarker ); break; case LLSDType.Binary: writer.Write( binaryNotationMarker ); writer.Write( "64" ); writer.Write( doubleQuotesNotationMarker ); writer.Write( llsd.AsString()); writer.Write( doubleQuotesNotationMarker ); break; case LLSDType.Date: writer.Write( dateNotationMarker ); writer.Write( doubleQuotesNotationMarker ); writer.Write( llsd.AsString()); writer.Write( doubleQuotesNotationMarker ); break; case LLSDType.URI: writer.Write( uriNotationMarker ); writer.Write( doubleQuotesNotationMarker ); writer.Write( EscapeCharacter( llsd.AsString(), doubleQuotesNotationMarker )); writer.Write( doubleQuotesNotationMarker ); break; case LLSDType.Array: SerializeNotationArray( writer, (LLSDArray)llsd ); break; case LLSDType.Map: SerializeNotationMap( writer, (LLSDMap)llsd ); break; default: throw new LLSDException( "Notation serialization: Not existing element discovered." ); } } private static void SerializeNotationArray( StringWriter writer, LLSDArray llsdArray ) { writer.Write( arrayBeginNotationMarker ); int lastIndex = llsdArray.Count - 1; for( int idx = 0; idx <= lastIndex; idx++ ) { SerializeNotationElement( writer, llsdArray[idx] ); if ( idx < lastIndex ) writer.Write( kommaNotationDelimiter ); } writer.Write( arrayEndNotationMarker ); } private static void SerializeNotationMap( StringWriter writer, LLSDMap llsdMap ) { writer.Write( mapBeginNotationMarker ); int lastIndex = llsdMap.Count - 1; int idx = 0; foreach( KeyValuePair kvp in llsdMap ) { writer.Write( singleQuotesNotationMarker ); writer.Write( EscapeCharacter( kvp.Key, singleQuotesNotationMarker )); writer.Write( singleQuotesNotationMarker ); writer.Write( keyNotationDelimiter ); SerializeNotationElement( writer,kvp.Value ); if ( idx < lastIndex ) writer.Write( kommaNotationDelimiter ); idx++; } writer.Write( mapEndNotationMarker ); } private static void SerializeNotationElementFormatted( StringWriter writer, string intend, LLSD llsd ) { switch( llsd.Type ) { case LLSDType.Unknown: writer.Write( undefNotationValue ); break; case LLSDType.Boolean: if ( llsd.AsBoolean() ) writer.Write( trueNotationValueTwo ); else writer.Write( falseNotationValueTwo ); break; case LLSDType.Integer: writer.Write( integerNotationMarker ); writer.Write( llsd.AsString()); break; case LLSDType.Real: writer.Write( realNotationMarker ); writer.Write( llsd.AsString()); break; case LLSDType.UUID: writer.Write( uuidNotationMarker ); writer.Write( llsd.AsString()); break; case LLSDType.String: writer.Write( singleQuotesNotationMarker ); writer.Write( EscapeCharacter( llsd.AsString(), singleQuotesNotationMarker )); writer.Write( singleQuotesNotationMarker ); break; case LLSDType.Binary: writer.Write( binaryNotationMarker ); writer.Write( "64" ); writer.Write( doubleQuotesNotationMarker ); writer.Write( llsd.AsString()); writer.Write( doubleQuotesNotationMarker ); break; case LLSDType.Date: writer.Write( dateNotationMarker ); writer.Write( doubleQuotesNotationMarker ); writer.Write( llsd.AsString()); writer.Write( doubleQuotesNotationMarker ); break; case LLSDType.URI: writer.Write( uriNotationMarker ); writer.Write( doubleQuotesNotationMarker ); writer.Write( EscapeCharacter( llsd.AsString(), doubleQuotesNotationMarker )); writer.Write( doubleQuotesNotationMarker ); break; case LLSDType.Array: SerializeNotationArrayFormatted( writer, intend + baseIntend, (LLSDArray)llsd ); break; case LLSDType.Map: SerializeNotationMapFormatted( writer, intend + baseIntend, (LLSDMap)llsd ); break; default: throw new LLSDException( "Notation serialization: Not existing element discovered." ); } } private static void SerializeNotationArrayFormatted( StringWriter writer, string intend, LLSDArray llsdArray ) { writer.Write( Helpers.NewLine ); writer.Write( intend ); writer.Write( arrayBeginNotationMarker ); int lastIndex = llsdArray.Count - 1; for( int idx = 0; idx <= lastIndex; idx++ ) { if ( llsdArray[idx].Type != LLSDType.Array && llsdArray[idx].Type != LLSDType.Map ) writer.Write( Helpers.NewLine ); writer.Write( intend + baseIntend ); SerializeNotationElementFormatted( writer, intend, llsdArray[idx] ); if ( idx < lastIndex ) { writer.Write( kommaNotationDelimiter ); } } writer.Write( Helpers.NewLine ); writer.Write( intend ); writer.Write( arrayEndNotationMarker ); } private static void SerializeNotationMapFormatted( StringWriter writer, string intend, LLSDMap llsdMap ) { writer.Write( Helpers.NewLine ); writer.Write( intend ); writer.Write( mapBeginNotationMarker ); writer.Write( Helpers.NewLine ); int lastIndex = llsdMap.Count - 1; int idx = 0; foreach( KeyValuePair kvp in llsdMap ) { writer.Write( intend + baseIntend ); writer.Write( singleQuotesNotationMarker ); writer.Write( EscapeCharacter( kvp.Key, singleQuotesNotationMarker )); writer.Write( singleQuotesNotationMarker ); writer.Write( keyNotationDelimiter ); SerializeNotationElementFormatted( writer, intend, kvp.Value ); if ( idx < lastIndex ) { writer.Write( Helpers.NewLine ); writer.Write( intend + baseIntend ); writer.Write( kommaNotationDelimiter ); writer.Write( Helpers.NewLine ); } idx++; } writer.Write( Helpers.NewLine ); writer.Write( intend ); writer.Write( mapEndNotationMarker ); } public static int PeekAndSkipWhitespace( StringReader reader ) { int character; while ((character = reader.Peek()) > 0 ) { char c = (char)character; if ( c == ' ' || c == '\t' || c == '\n' || c == '\r' ) { reader.Read(); continue; } else break; } return character; } public static int ReadAndSkipWhitespace( StringReader reader ) { int character = PeekAndSkipWhitespace( reader ); reader.Read(); return character; } public static int GetLengthInBrackets( StringReader reader ) { int character; StringBuilder s = new StringBuilder(); if ((( character = PeekAndSkipWhitespace( reader )) > 0 ) && ((char)character == sizeBeginNotationMarker)) { reader.Read(); } while((( character = reader.Read()) > 0) && Char.IsDigit((char)character) && ((char) character != sizeEndNotationMarker )) { s.Append((char)character); } if ( character < 0 ) throw new LLSDException( "Notation LLSD parsing: Can't parse length value cause unexpected end of stream." ); int length; if ( !Helpers.TryParse( s.ToString(), out length )) throw new LLSDException( "Notation LLSD parsing: Can't parse length value." ); return length; } public static string GetStringDelimitedBy( StringReader reader, char delimiter ) { int character; bool foundEscape = false; StringBuilder s = new StringBuilder(); while( ((character = reader.Read()) > 0 ) && (((char) character != delimiter ) || ((char) character == delimiter && foundEscape ))) { if ( foundEscape ) { foundEscape = false; switch((char)character) { case 'a': s.Append( '\a' ); break; case 'b': s.Append( '\b' ); break; case 'f': s.Append( '\f' ); break; case 'n': s.Append( '\n' ); break; case 'r': s.Append( '\r' ); break; case 't': s.Append( '\t' ); break; case 'v': s.Append( '\v' ); break; default: s.Append((char)character); break; } } else if ((char)character == '\\') foundEscape = true; else s.Append((char)character); } if ( character < 0) throw new LLSDException( "Notation LLSD parsing: Can't parse text because unexpected end of stream while expecting a '" + delimiter + "' character." ); return s.ToString(); } public static int BufferCharactersEqual( StringReader reader, char[] buffer, int offset ) { int character; int lastIndex = buffer.Length - 1; int crrIndex = offset; bool charactersEqual = true; while (( character = reader.Peek()) > 0 && crrIndex <= lastIndex && charactersEqual ) { if (((char) character) != buffer[crrIndex] ) { charactersEqual = false; break; } crrIndex++; reader.Read(); } return crrIndex; } public static string UnescapeCharacter( String s, char c ) { string oldOne = "\\" + c; string newOne = new String( c, 1 ); String sOne = s.Replace( "\\\\", "\\" ).Replace( oldOne, newOne ); return sOne; } public static string EscapeCharacter( String s, char c ) { string oldOne = new String( c, 1 ); string newOne = "\\" + c; String sOne = s.Replace( "\\", "\\\\" ).Replace( oldOne, newOne ); return sOne; } } }