//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ #if OBJECTSTATEFORMATTER namespace System.Web.UI { using System; using System.IO; using System.Text; /// /// Serializes Web Froms view state. The limited object serialization (LOS) /// formatter is designed for ASCII format serialization. This class /// supports serializing any object graph, but is optimized for those containing /// strings, arrays, and hashtables. It offers second order optimization for many of /// the .NET primitive types. /// This class has been replaced with a more optimal serialization mechanism implemented /// in LosSerializer. LosFormatter itself uses LosSerialization as part of its /// implementation to benefit from the highly compact serialization when possible. /// public sealed class LosFormatter { private const int InitialBufferSize = 24; private ObjectStateFormatter _formatter; private bool _enableMac; /// /// Creates a LosFormatter object. /// public LosFormatter() : this(false, (byte[])null) { } /// /// Creates a LosFormatter object, specifying whether view state mac should be /// enabled. If it is, use macKeyModifier to modify the mac key. /// public LosFormatter(bool enableMac, string macKeyModifier): this (enableMac, GetBytes(macKeyModifier)) { } public LosFormatter(bool enableMac, byte[] macKeyModifier) { _enableMac = enableMac; if (enableMac) { _formatter = new ObjectStateFormatter(macKeyModifier); } else { _formatter = new ObjectStateFormatter(); } } private static byte[] GetBytes(string s) { if (s != null && s.Length != 0) return Encoding.Unicode.GetBytes(s); else return null; } /// /// Deserializes a LOS-formatted object from a object. /// public object Deserialize(Stream stream) { TextReader input = null; input = new StreamReader(stream); return Deserialize(input); } /// /// Deserializes a LOS-formatted object from a object. /// public object Deserialize(TextReader input) { char[] data = new char[128]; int read = 0; int current = 0; int blockSize = InitialBufferSize; do { read = input.Read(data, current, blockSize); current += read; if (current > data.Length - blockSize) { char[] bigger = new char[data.Length * 2]; Array.Copy(data, bigger, data.Length); data = bigger; } } while (read == blockSize); return Deserialize(new String(data, 0, current)); } /// /// Deserializes a LOS formatted object from a string. /// public object Deserialize(string input) { return _formatter.Deserialize(input); } /// /// Serializes the Web Forms view state value into /// a object. /// public void Serialize(Stream stream, object value) { TextWriter output = new StreamWriter(stream); SerializeInternal(output, value); output.Flush(); } /// /// Serializes the view state value into a object. /// public void Serialize(TextWriter output, object value) { SerializeInternal(output, value); } /// /// Serialized value into the writer. /// private void SerializeInternal(TextWriter output, object value) { string data = _formatter.Serialize(value); output.Write(data); } } } #else // !OBJECTSTATEFORMATTER // uncomment for "human readable" debugging output - no base64 encoding. //#define NO_BASE64 namespace System.Web.UI { using System.Runtime.Serialization.Formatters.Binary; using System.Runtime.Serialization; using System; using System.IO; using System.Security.Principal; using System.Collections; using System.Collections.Specialized; using System.Diagnostics; using System.ComponentModel; using System.Globalization; using System.Threading; using System.Text; using System.Web.Configuration; using System.Security.Permissions; /// /// Serializes Web Froms view state. The limited object serialization (LOS) /// formatter is designed for highly compact ASCII format serialization. This class /// supports serializing any object graph, but is optimized for those containing /// strings, arrays, and hashtables. It offers second order optimization for many of /// the .NET primitive types. /// public sealed class LosFormatter : IStateFormatter { // NOTE : This formatter is not very fault tolerant, by design. I want // : to avoid a bunch of reduntant checking... Since the format // : is short lived we shouldn't have to worry about it. // // NOTE : Although hex encoding of numbers would be more effecient, it // : would make it much harder to determine what are numbers vs. // : names & types. Unless this becomes a problem, I suggest we // : keep encoding in decimal. // // Known Types. There can only be 50 of these... The order shouldn't // matter, we store and index into this array... although there is a // slight perf advantage being at the top of the list... // private static readonly Type[] knownTypes = new Type[] { typeof(object), typeof(System.Web.UI.WebControls.Unit), typeof(System.Drawing.Color), typeof(System.Int16), typeof(System.Int64), }; static readonly Encoding EncodingInstance = new UTF8Encoding(false); static readonly NumberFormatInfo NumberFormat = NumberFormatInfo.InvariantInfo; private const int UntypedTypeId = -1; private const int NoTypeId = -2; private const int InitialBufferSize = 24; private const int BufferGrowth = 48; // Constant chars and strings... you can change these and all references to the // begin, end, and delimiter chars are fixed up private const char leftAngleBracketChar = '<'; private const char rightAngleBracketChar = '>'; private const char valueDelimiterChar = ';'; private static readonly char[] escapedCharacters = { leftAngleBracketChar, rightAngleBracketChar, valueDelimiterChar, '\\' }; private static CharBufferAllocator _charBufferAllocator = new CharBufferAllocator(256, 16); // reusable Temp buffer used for constructing strings from char arrays... this is // more performant than using a StringBuilder. // private char[] _builder; private bool _recyclable; // Tables used to build up the type and name tables during // serialization. Not used during deserilization. private IDictionary _typeTable; // Deserialization variables. Not used during serialization. private ArrayList _deserializedTypeTable; private ListDictionary _deserializedConverterTable; private char[] _deserializationData; private int _current; // MAC authentication private bool _enableViewStateMac; private bool EnableViewStateMac { get { return _enableViewStateMac; } } private byte [] _macKey = null; /// /// Creates a LosFormatter object. /// public LosFormatter() {} /// /// Creates a LosFormatter object, specifying whether view state mac should be /// enabled. If it is, use macKeyModifier to modify the mac key. /// public LosFormatter(bool enableMac, string macKeyModifier) { _enableViewStateMac = enableMac; if (macKeyModifier != null) _macKey = Encoding.Unicode.GetBytes(macKeyModifier); } /// /// Deserializes a LOS-formatted object from a object. /// public object Deserialize(Stream stream) { TextReader input = null; input = new StreamReader(stream); return Deserialize(input); } /// /// Deserializes a LOS-formatted object from a object. /// public object Deserialize(TextReader input) { char[] data = new char[128]; int read = 0; int current = 0; int blockSize = InitialBufferSize; do { read = input.Read(data, current, blockSize); current += read; if (current > data.Length - blockSize) { char[] bigger = new char[data.Length * 2]; Array.Copy(data, bigger, data.Length); data = bigger; } } while (read == blockSize); return Deserialize(new String(data, 0, current)); } /// /// Deserializes a LOS formatted object from a string. /// public object Deserialize(string input) { #if NO_BASE64 char[] data = input.ToCharArray(); #else byte[] dataBytes = Convert.FromBase64String(input); int dataLength = -1; if (EnableViewStateMac) { try { dataBytes = MachineKeySection.GetDecodedData(dataBytes, _macKey, 0, dataBytes.Length, ref dataLength); } catch (Exception e) { PerfCounters.IncrementCounter(AppPerfCounter.VIEWSTATE_MAC_FAIL); ViewStateException.ThrowMacValidationError(e, input); } } if (dataLength == -1) { dataLength = dataBytes.Length; } char[] data = EncodingInstance.GetChars(dataBytes, 0, dataLength); #endif // clear or allocate name and type tables. // if (_deserializedTypeTable == null) { _deserializedTypeTable = new ArrayList(); _deserializedConverterTable = new ListDictionary(); } else { _deserializedTypeTable.Clear(); _deserializedConverterTable.Clear(); } _builder = (char[]) _charBufferAllocator.GetBuffer(); _recyclable = true; // DeserializeValueInternal is recursive, so we just kick this off // starting at 0 _current = 0; _deserializationData = data; object ret = DeserializeValueInternal(); if (_recyclable) _charBufferAllocator.ReuseBuffer(_builder); return ret; } /// /// Deserializes a value from tokens, starting at current. When this /// function returns, current will be left at the next token. /// /// This function is recursive. /// private object DeserializeValueInternal() { // Determine the data type... possible combinations are: // // @<...> == array of strings // @T<...> == array of (typeref T) // b<...> == base64 encoded value // h<...> == hashtable // l<...> == arraylist // p<...> == pair // t<...> == triplet // i<...> == int // o == boolean true/false // T<...> == (typeref T) // ... == string // object value = null; string token = ConsumeOneToken(); if (_current >= _deserializationData.Length || _deserializationData[_current] != leftAngleBracketChar) { // just a string - next token is not a left angle bracket // we can shortcut here and just return the string //_current++; //consume right angle bracket or delimiter return token; } _current++; // consume left angle bracket // otherwise, we have typeref followed by value if (token.Length == 1) { // simple type we recognize char ch = token[0]; if (ch == 'p') { Pair p = new Pair(); if (_deserializationData[_current] != valueDelimiterChar) { p.First = DeserializeValueInternal(); } _current++; // consume delimeter if (_deserializationData[_current] != rightAngleBracketChar) { p.Second = DeserializeValueInternal(); } value = p; } else if (ch == 't') { Triplet t = new Triplet(); if (_deserializationData[_current] != valueDelimiterChar) { t.First = DeserializeValueInternal(); } _current++; // consume delimeter if (_deserializationData[_current] != valueDelimiterChar) { t.Second = DeserializeValueInternal(); } _current++; // consume delimeter if (_deserializationData[_current] != rightAngleBracketChar) { t.Third = DeserializeValueInternal(); } value = t; } // Parse int32... else if (ch == 'i') { value = Int32.Parse(ConsumeOneToken(), NumberFormat); } else if (ch == 'o') { value = _deserializationData[_current] == 't'; _current++; // consume t or f } // Parse arrayList... // else if (ch == 'l') { ArrayList data = new ArrayList(); while (_deserializationData[_current] != rightAngleBracketChar) { object itemValue = null; if (_deserializationData[_current] != valueDelimiterChar) { itemValue = DeserializeValueInternal(); } data.Add(itemValue); _current++; //consume the delimiter } value = data; } else if (ch == '@') { // if we're here, length == 1 so this is a string array value = ConsumeStringArray(); } // Parse hashtable... // else if (ch == 'h') { Hashtable data = new Hashtable(); while (_deserializationData[_current] != rightAngleBracketChar) { object key; key = DeserializeValueInternal(); // hashtable key cannot be null _current++; // consume delimiter if (_deserializationData[_current] != valueDelimiterChar) { data[key] = DeserializeValueInternal(); } else { data[key] = null; } _current++; // consume delimiter } value = data; } // base64 encoded... // else if (ch == 'b') { string text = ConsumeOneToken(); byte[] serializedData; serializedData = Convert.FromBase64String(text); if (!String.IsNullOrEmpty(serializedData)) { System.Runtime.Serialization.IFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); value = formatter.Deserialize(new MemoryStream(serializedData)); } } // Parse typeconverter value ... // else { // we have a typeref which is only one character long value = ConsumeTypeConverterValue(token); } } else { // length > 1 // Parse array... // if (token[0] == '@') { // if we're here, length > 1 so we must have a type ref after the @ Type creatableType = TypeFromTypeRef(token.Substring(1)); value = ConsumeArray(creatableType); } // Parse typeconverter value ... // else { // we have a typeref which is more than one character long value = ConsumeTypeConverterValue(token); } } _current++; //consume right angle bracket return value; } private string ConsumeOneToken() { int locInBuilder = 0; while (_current < _deserializationData.Length) { switch (_deserializationData[_current]) { case '\\': _current++; // skip slash if (_deserializationData[_current] == 'e') { _current++; return String.Empty; } _builder[locInBuilder] = _deserializationData[_current]; locInBuilder++; break; case valueDelimiterChar: case leftAngleBracketChar: case rightAngleBracketChar: return new string(_builder, 0, locInBuilder); default: _builder[locInBuilder] = _deserializationData[_current]; locInBuilder++; break; } _current++; // Alloc _builder always 2 greater than locInBuilder to make sure // we can do nested/escape parsing without error... // if (locInBuilder >= _builder.Length) { char[] bigger = new char[_builder.Length + BufferGrowth]; Array.Copy(_builder, bigger, _builder.Length); _builder = bigger; _recyclable = false; } } return new string(_builder, 0, locInBuilder); } private object ConsumeStringArray() { ArrayList data = new ArrayList(); while (_deserializationData[_current] != rightAngleBracketChar) { object itemValue = null; if (_deserializationData[_current] != valueDelimiterChar) { itemValue = ConsumeOneToken(); } data.Add(itemValue); _current++; //consume the delimiter } return data.ToArray(typeof(string)); } private object ConsumeArray(Type creatableType) { ArrayList data = new ArrayList(); while (_deserializationData[_current] != rightAngleBracketChar) { object itemValue = null; if (_deserializationData[_current] != valueDelimiterChar) { itemValue = DeserializeValueInternal(); } data.Add(itemValue); _current++; //consume the delimiter } return data.ToArray(creatableType); } private object ConsumeTypeConverterValue(string token) { int typeref = ParseNumericString(token); TypeConverter tc; if (typeref != -1) { // token is the string representation of the number here tc = (TypeConverter) _deserializedConverterTable[token]; if (tc == null) { // wasn't in the converter table, add it now // we need this case because arrays can add types but not typeconverters Type t = TypeFromTypeCode(typeref); tc = TypeDescriptor.GetConverter(t); _deserializedConverterTable[token] = tc; } } else { // it's just a name, lookup type and add to type table Type t = Type.GetType(token); tc = TypeDescriptor.GetConverter(t); // add to type table and converter table. _deserializedConverterTable[(_deserializedTypeTable.Count + 50).ToString(NumberFormat)] = tc; _deserializedTypeTable.Add(t); } string text = ConsumeOneToken(); return tc.ConvertFrom(null, CultureInfo.InvariantCulture, text); } /// /// Serializes the Web Forms view state value into /// a object. /// public void Serialize(Stream stream, object value) { TextWriter output = new StreamWriter(stream); SerializeInternal(output, value); output.Flush(); } /// /// Serializes the view state value into a object. /// public void Serialize(TextWriter output, object value) { SerializeInternal(output, value); } /// /// Serialized value into the writer. /// private void SerializeInternal(TextWriter output, object value) { if (value == null) return; if (_typeTable == null) _typeTable = new HybridDictionary(); else _typeTable.Clear(); #if NO_BASE64 SerializeValue(output, value); #else LosWriter writer = new LosWriter(); SerializeValue(writer, value); writer.CompleteTransforms(output, EnableViewStateMac, _macKey); writer.Dispose(); #endif } /// /// Recursively serializes value into the writer. /// private void SerializeValue(TextWriter output, object value) { if (value == null) return; // First determine the type... either typeless (string), array, // typed array, hashtable, pair, triplet, knowntype, typetable reference, or // type... // // serialize string... // if (value is string) { WriteEscapedString(output, (string)value); } // serialize Int32... // else if (value is Int32) { output.Write('i'); output.Write(leftAngleBracketChar); output.Write(((Int32)value).ToString(NumberFormat)); output.Write(rightAngleBracketChar); } else if (value is Boolean) { output.Write('o'); output.Write(leftAngleBracketChar); output.Write( ((bool) value) ? 't' : 'f'); output.Write(rightAngleBracketChar); } // serialize arraylist... // else if (value is ArrayList) { output.Write('l'); output.Write(leftAngleBracketChar); ArrayList ar = (ArrayList)value; int c = ar.Count; for (int i=0; i /// Takes a typeRef, and converts it to a Type. Either by returning /// Type.GetType(typeRef), or looking it up. /// private Type TypeFromTypeRef(string typeRef) { int number = ParseNumericString(typeRef); Type t = TypeFromTypeCode(number); if (t != null) return t; // it's just a name, lookup type and add to type table t = Type.GetType(typeRef); _deserializedTypeTable.Add(t); return t; } private Type TypeFromTypeCode(int number) { if (number != -1) { // it is a type id, either in the known table or in our type table if (number <= 49) return knownTypes[number]; return (Type) _deserializedTypeTable[number - 50]; } return null; } // Note : We have to determine if "typeRef" is a number. The easiest // : and fastest way to do this is to walk the string. While // : we are doing this, lets build up the number... after // : all this is much faster than Int32.Parse // private int ParseNumericString(string num) { int number = 0; int len = num.Length; for (int i=0; i /// Escapes and writes the escaped value of str into the writer. /// private void WriteEscapedString(TextWriter output, string str) { if (str == null) return; // need to "escape" the empty string to distinguish it // from a null value if (str.Length == 0) { output.Write('\\'); output.Write('e'); return; } int first = str.IndexOfAny(escapedCharacters); if (first == -1) { output.Write(str); } else { char[] strData = str.ToCharArray(); output.Write(strData, 0, first); int len = strData.Length; for (int i=first; i