//------------------------------------------------------------------------------
//
// 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