|
//------------------------------------------------------------------------------
// <copyright file="ObjectStateFormatter.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.UI {
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Globalization;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Web.Compilation;
using System.Web.Configuration;
using System.Web.Util;
using System.Web.Management;
using System.Web.UI.WebControls;
using System.Web.Security.Cryptography;
//
/// <devdoc>
/// ObjectStateFormatter is designed to efficiently serialize arbitrary object graphs
/// that represent the state of an object (decomposed into simpler types) into
/// a highly compact binary or ASCII representations.
/// The formatter contains native support for optimized serialization of a fixed
/// set of known types such as ints, shorts, booleans, strings, other primitive types
/// arrays, Pairs, Triplets, ArrayLists, Hashtables etc. In addition it utilizes
/// TypeConverters for semi-optimized serialization of custom types. Finally, it uses
/// binary serialization as a fallback mechanism. The formatter is also able to compress
/// IndexedStrings contained in the object graph.
/// </devdoc>
public sealed class ObjectStateFormatter : IStateFormatter, IStateFormatter2, IFormatter {
// Optimized type tokens
private const byte Token_Int16 = 1;
private const byte Token_Int32 = 2;
private const byte Token_Byte = 3;
private const byte Token_Char = 4;
private const byte Token_String = 5;
private const byte Token_DateTime = 6;
private const byte Token_Double = 7;
private const byte Token_Single = 8;
private const byte Token_Color = 9;
private const byte Token_KnownColor = 10;
private const byte Token_IntEnum = 11;
private const byte Token_EmptyColor = 12;
private const byte Token_Pair = 15;
private const byte Token_Triplet = 16;
private const byte Token_Array = 20;
private const byte Token_StringArray = 21;
private const byte Token_ArrayList = 22;
private const byte Token_Hashtable = 23;
private const byte Token_HybridDictionary = 24;
private const byte Token_Type = 25;
// private const byte Token_Nullable = 26; Removed per DevDiv 165426
// Background: Used to support nullables as a special case, CLR added support for this
// but they forgot to remove the deserialization code when they removed the support
// potentially Beta2 customers could have serialized data (WebParts) which have this token.
// We removed support since this was broken anyways in RTM.
private const byte Token_Unit = 27;
private const byte Token_EmptyUnit = 28;
private const byte Token_EventValidationStore = 29;
// String-table optimized strings
private const byte Token_IndexedStringAdd = 30;
private const byte Token_IndexedString = 31;
// Semi-optimized (TypeConverter-based)
private const byte Token_StringFormatted = 40;
// Semi-optimized (Types)
private const byte Token_TypeRefAdd = 41;
private const byte Token_TypeRefAddLocal = 42;
private const byte Token_TypeRef = 43;
// Un-optimized (Binary serialized) types
private const byte Token_BinarySerialized = 50;
// Optimized for sparse arrays
private const byte Token_SparseArray = 60;
// Constant values
private const byte Token_Null = 100;
private const byte Token_EmptyString = 101;
private const byte Token_ZeroInt32 = 102;
private const byte Token_True = 103;
private const byte Token_False = 104;
// Known types for which we generate short type references
// rather than assembly qualified names
//
private static readonly Type[] KnownTypes =
new Type[] {
typeof(object),
typeof(int),
typeof(string),
typeof(bool)
};
// Format and Version
private const byte Marker_Format = 0xFF;
private const byte Marker_Version_1 = 0x01;
// The size of the string table. At most it can be Byte.MaxValue.
//
private const int StringTableSize = Byte.MaxValue;
// Used during serialization
private IDictionary _typeTable;
private IDictionary _stringTable;
// Used during deserialization
private IList _typeList;
// Used during both serialization and deserialization
private int _stringTableCount;
private string[] _stringList;
// Used for performing Mac-encoding when this LosSerializer is used
// in view state serialization.
private byte[] _macKeyBytes;
private readonly bool _forceLegacyCryptography;
// Combined with Purpose objects which are passed in during serialization / deserialization.
private List<string> _specificPurposes;
// If true, this class will throw an exception if it cannot deserialize a type or value.
// If false, this class will use insert "null" if it cannot deserialize a type or value.
// Default is true, WebParts Personalization sets this to false.
private bool _throwOnErrorDeserializing;
// We use page to determine whether to to encrypt or decrypt based on Page.RequiresViewStateEncryptionInternal or Page.ContainsEncryptedViewstate
private Page _page;
/// <devdoc>
/// Initializes a new instance of the ObjectStateFormatter.
/// </devdoc>
public ObjectStateFormatter() : this(null) {
}
/// <internalonly/>
/// <devdoc>
/// Initializes a new instance of the ObjectStateFormatter. A MAC encoding
/// key can be specified to have the serialized data encoded for view state
/// purposes.
/// NOTE: this constructor is mainly for LOSFormatter's consumption, not used internally
/// </devdoc>
internal ObjectStateFormatter(byte[] macEncodingKey) : this(null, true) {
_macKeyBytes = macEncodingKey;
if (macEncodingKey != null) {
// If the developer explicitly asked for the data to be signed, we must honor that.
_forceLegacyCryptography = true;
}
}
/// <internalonly/>
/// <devdoc>
/// Initializes a new instance of the ObjectStateFormatter. A MAC encoding
/// key can be specified to have the serialized data encoded for view state
/// purposes. The Page object is used to determine whether the viewstate will be encrypted
/// for serialize and deserialize.
/// </devdoc>
internal ObjectStateFormatter(Page page, bool throwOnErrorDeserializing) {
_page = page;
_throwOnErrorDeserializing = throwOnErrorDeserializing;
}
// This will return a list of specific purposes (for cryptographic subkey generation).
internal List<string> GetSpecificPurposes() {
if (_specificPurposes == null) {
// Only generate a specific purpose list if we have a Page
if (_page == null) {
return null;
}
// Note: duplicated (somewhat) in GetMacKeyModifier, keep in sync
// See that method for comments on why these modifiers are in place
List<string> specificPurposes = new List<string>() {
"TemplateSourceDirectory: " + _page.TemplateSourceDirectory.ToUpperInvariant(),
"Type: " + _page.GetType().Name.ToUpperInvariant()
};
if (_page.ViewStateUserKey != null) {
specificPurposes.Add("ViewStateUserKey: " + _page.ViewStateUserKey);
}
_specificPurposes = specificPurposes;
}
return _specificPurposes;
}
// This will return the MacKeyModifier provided in the LOSFormatter constructor or
// generate one from Page if EnableViewStateMac is true.
private byte[] GetMacKeyModifier() {
if (_macKeyBytes == null) {
// Only generate a MacKeyModifier if we have a page
if (_page == null) {
return null;
}
// Note: duplicated (somewhat) in GetSpecificPurposes, keep in sync
// Use the page's directory and class name as part of the key (ASURT 64044)
uint pageHashCode = _page.GetClientStateIdentifier();
string viewStateUserKey = _page.ViewStateUserKey;
if (viewStateUserKey != null) {
// Modify the key with the ViewStateUserKey, if any (ASURT 126375)
int count = Encoding.Unicode.GetByteCount(viewStateUserKey);
_macKeyBytes = new byte[count + 4];
Encoding.Unicode.GetBytes(viewStateUserKey, 0, viewStateUserKey.Length, _macKeyBytes, 4);
}
else {
_macKeyBytes = new byte[4];
}
_macKeyBytes[0] = (byte)pageHashCode;
_macKeyBytes[1] = (byte)(pageHashCode >> 8);
_macKeyBytes[2] = (byte)(pageHashCode >> 16);
_macKeyBytes[3] = (byte)(pageHashCode >> 24);
}
return _macKeyBytes;
}
/// <devdoc>
/// Adds a string reference during the deserialization process
/// to support deserialization of IndexedStrings.
/// The string is added to the string list on the fly, so it is available
/// for future reference by index.
/// </devdoc>
private void AddDeserializationStringReference(string s) {
Debug.Assert((s != null) && (s.Length != 0));
if (_stringTableCount == StringTableSize) {
// loop around to the start of the table
_stringTableCount = 0;
}
_stringList[_stringTableCount] = s;
_stringTableCount++;
}
/// <devdoc>
/// Adds a type reference during the deserialization process,
/// so that it can be referred to later by its index.
/// </devdoc>
private void AddDeserializationTypeReference(Type type) {
// Type may be null, if there is no longer a Type on the system with the saved type name.
// This is unlikely to happen with a Type stored in ViewState, but more likely with a Type
// stored in Personalization.
_typeList.Add(type);
}
/// <devdoc>
/// Adds a string reference during the serialization process to support
/// the serialization of IndexedStrings.
/// The string is added to the string list, as well as to a string table
/// for quick lookup.
/// </devdoc>
private void AddSerializationStringReference(string s) {
Debug.Assert((s != null) && (s.Length != 0));
if (_stringTableCount == StringTableSize) {
// loop around to the start of the table
_stringTableCount = 0;
}
string oldString = _stringList[_stringTableCount];
if (oldString != null) {
// it means we're looping around, and the existing table entry
// needs to be removed, as a new one will replace it
Debug.Assert(_stringTable.Contains(oldString));
_stringTable.Remove(oldString);
}
_stringTable[s] = _stringTableCount;
_stringList[_stringTableCount] = s;
_stringTableCount++;
}
/// <devdoc>
/// Adds a type reference during the serialization process, so it
/// can be later referred to by its index.
/// </devdoc>
private void AddSerializationTypeReference(Type type) {
Debug.Assert(type != null);
int typeID = _typeTable.Count;
_typeTable[type] = typeID;
}
[SecurityPermission(SecurityAction.Assert, Flags = SecurityPermissionFlag.SerializationFormatter)]
internal object DeserializeWithAssert(Stream inputStream) {
return Deserialize(inputStream);
}
/// <devdoc>
/// Deserializes an object graph from its binary serialized form
/// contained in the specified stream.
/// </devdoc>
public object Deserialize(Stream inputStream) {
if (inputStream == null) {
throw new ArgumentNullException("inputStream");
}
Exception deserializationException = null;
InitializeDeserializer();
SerializerBinaryReader reader = new SerializerBinaryReader(inputStream);
try {
byte formatMarker = reader.ReadByte();
if (formatMarker == Marker_Format) {
byte versionMarker = reader.ReadByte();
Debug.Assert(versionMarker == Marker_Version_1);
if (versionMarker == Marker_Version_1) {
return DeserializeValue(reader);
}
}
}
catch (Exception e) {
deserializationException = e;
}
// throw an exception if there was an exception during deserialization
// or if deserialization was skipped because of invalid format or
// version data in the stream
throw new ArgumentException(SR.GetString(SR.InvalidSerializedData), deserializationException);
}
/// <devdoc>
/// Deserializes an object graph from its textual serialized form
/// contained in the specified string.
/// </devdoc>
public object Deserialize(string inputString) {
// If the developer called Deserialize() manually on an ObjectStateFormatter object that was configured
// for cryptographic operations, he wouldn't have been able to specify a Purpose. We'll just provide
// a default value for him.
return Deserialize(inputString, Purpose.User_ObjectStateFormatter_Serialize);
}
private object Deserialize(string inputString, Purpose purpose) {
if (String.IsNullOrEmpty(inputString)) {
throw new ArgumentNullException("inputString");
}
byte[] inputBytes = Convert.FromBase64String(inputString);
int length = inputBytes.Length;
#if !FEATURE_PAL // FEATURE_PAL does not enable cryptography
try {
if (AspNetCryptoServiceProvider.Instance.IsDefaultProvider && !_forceLegacyCryptography) {
// If we're configured to use the new crypto providers, call into them if encryption or signing (or both) is requested.
if (_page != null && (_page.ContainsEncryptedViewState || _page.EnableViewStateMac)) {
Purpose derivedPurpose = purpose.AppendSpecificPurposes(GetSpecificPurposes());
ICryptoService cryptoService = AspNetCryptoServiceProvider.Instance.GetCryptoService(derivedPurpose);
byte[] clearData = cryptoService.Unprotect(inputBytes);
inputBytes = clearData;
length = clearData.Length;
}
}
else {
// Otherwise go through legacy crypto mechanisms
#pragma warning disable 618 // calling obsolete methods
if (_page != null && _page.ContainsEncryptedViewState) {
inputBytes = MachineKeySection.EncryptOrDecryptData(false, inputBytes, GetMacKeyModifier(), 0, length);
length = inputBytes.Length;
}
// We need to decode if the page has EnableViewStateMac or we got passed in some mac key string
else if ((_page != null && _page.EnableViewStateMac) || _macKeyBytes != null) {
inputBytes = MachineKeySection.GetDecodedData(inputBytes, GetMacKeyModifier(), 0, length, ref length);
}
#pragma warning restore 618 // calling obsolete methods
}
}
catch {
// MSRC 10405: Don't propagate inner exceptions, as they may contain sensitive cryptographic information.
PerfCounters.IncrementCounter(AppPerfCounter.VIEWSTATE_MAC_FAIL);
ViewStateException.ThrowMacValidationError(null, inputString);
}
#endif // !FEATURE_PAL
object result = null;
MemoryStream objectStream = GetMemoryStream();
try {
objectStream.Write(inputBytes, 0, length);
objectStream.Position = 0;
result = Deserialize(objectStream);
}
finally {
ReleaseMemoryStream(objectStream);
}
return result;
}
/// <devdoc>
/// Deserializes an IndexedString. An IndexedString can either be the string itself (the
/// first occurrence), or a reference to it by index into the string table.
/// </devdoc>
private IndexedString DeserializeIndexedString(SerializerBinaryReader reader, byte token) {
Debug.Assert((token == Token_IndexedStringAdd) || (token == Token_IndexedString));
if (token == Token_IndexedString) {
// reference to string in the current string table
int tableIndex = (int)reader.ReadByte();
Debug.Assert(_stringList[tableIndex] != null);
return new IndexedString(_stringList[tableIndex]);
}
else {
// first occurrence of this indexed string. Read in the string, and add
// a reference to it, so future references can be resolved.
string s = reader.ReadString();
AddDeserializationStringReference(s);
return new IndexedString(s);
}
}
/// <devdoc>
/// Deserializes a Type. A Type can either be its name (the first occurrence),
/// or a reference to it by index into the type table. If we cannot load the type,
/// we throw an exception if _throwOnErrorDeserializing is true, and we return null if
/// _throwOnErrorDeserializing is false.
/// </devdoc>
private Type DeserializeType(SerializerBinaryReader reader) {
byte token = reader.ReadByte();
Debug.Assert((token == Token_TypeRef) ||
(token == Token_TypeRefAdd) ||
(token == Token_TypeRefAddLocal));
if (token == Token_TypeRef) {
// reference by index into type table
int typeID = reader.ReadEncodedInt32();
return (Type)_typeList[typeID];
}
else {
// first occurrence of this type. Read in the type, resolve it, and
// add it to the type table
string typeName = reader.ReadString();
Type resolvedType = null;
try {
if (token == Token_TypeRefAddLocal) {
resolvedType = HttpContext.SystemWebAssembly.GetType(typeName, true);
}
else {
resolvedType = Type.GetType(typeName, true);
}
}
catch (Exception exception) {
if (_throwOnErrorDeserializing) {
throw;
}
else {
// Log error message
WebBaseEvent.RaiseSystemEvent(
SR.GetString(SR.Webevent_msg_OSF_Deserialization_Type, typeName),
this,
WebEventCodes.WebErrorObjectStateFormatterDeserializationError,
WebEventCodes.UndefinedEventDetailCode,
exception);
}
}
AddDeserializationTypeReference(resolvedType);
return resolvedType;
}
}
/// <devdoc>
/// Deserializes a single value from the underlying stream.
/// Essentially a token is read, followed by as much data needed to recreate
/// the single value.
/// </devdoc>
private object DeserializeValue(SerializerBinaryReader reader) {
byte token = reader.ReadByte();
// NOTE: Preserve the order here with the order of the logic in
// the SerializeValue method.
switch (token) {
case Token_Null:
return null;
case Token_EmptyString:
return String.Empty;
case Token_String:
return reader.ReadString();
case Token_ZeroInt32:
return 0;
case Token_Int32:
return reader.ReadEncodedInt32();
case Token_Pair:
return new Pair(DeserializeValue(reader),
DeserializeValue(reader));
case Token_Triplet:
return new Triplet(DeserializeValue(reader),
DeserializeValue(reader),
DeserializeValue(reader));
case Token_IndexedString:
case Token_IndexedStringAdd:
return DeserializeIndexedString(reader, token);
case Token_ArrayList:
{
int count = reader.ReadEncodedInt32();
ArrayList list = new ArrayList(count);
for (int i = 0; i < count; i++) {
list.Add(DeserializeValue(reader));
}
return list;
}
case Token_True:
return true;
case Token_False:
return false;
case Token_Byte:
return reader.ReadByte();
case Token_Char:
return reader.ReadChar();
case Token_DateTime:
return DateTime.FromBinary(reader.ReadInt64());
case Token_Double:
return reader.ReadDouble();
case Token_Int16:
return reader.ReadInt16();
case Token_Single:
return reader.ReadSingle();
case Token_Hashtable:
case Token_HybridDictionary:
{
int count = reader.ReadEncodedInt32();
IDictionary table;
if (token == Token_Hashtable) {
table = new Hashtable(count);
}
else {
table = new HybridDictionary(count);
}
for (int i = 0; i < count; i++) {
table.Add(DeserializeValue(reader),
DeserializeValue(reader));
}
return table;
}
case Token_Type:
return DeserializeType(reader);
case Token_StringArray:
{
int count = reader.ReadEncodedInt32();
string[] array = new string[count];
for (int i = 0; i < count; i++) {
array[i] = reader.ReadString();
}
return array;
}
case Token_Array:
{
Type elementType = DeserializeType(reader);
int count = reader.ReadEncodedInt32();
Array list = Array.CreateInstance(elementType, count);
for (int i = 0; i < count; i++) {
list.SetValue(DeserializeValue(reader), i);
}
return list;
}
case Token_IntEnum:
{
Type enumType = DeserializeType(reader);
int enumValue = reader.ReadEncodedInt32();
return Enum.ToObject(enumType, enumValue);
}
case Token_Color:
return Color.FromArgb(reader.ReadInt32());
case Token_EmptyColor:
return Color.Empty;
case Token_KnownColor:
return Color.FromKnownColor((KnownColor)reader.ReadEncodedInt32());
case Token_Unit:
return new Unit(reader.ReadDouble(), (UnitType)reader.ReadInt32());
case Token_EmptyUnit:
return Unit.Empty;
case Token_EventValidationStore:
return EventValidationStore.DeserializeFrom(reader.BaseStream);
case Token_SparseArray:
{
Type elementType = DeserializeType(reader);
int count = reader.ReadEncodedInt32();
int itemCount = reader.ReadEncodedInt32();
// Guard against bad data
if (itemCount > count) {
throw new InvalidOperationException(SR.GetString(SR.InvalidSerializedData));
}
Array list = Array.CreateInstance(elementType, count);
for (int i = 0; i < itemCount; ++i) {
// Data is encoded as <index, Item>
int nextPos = reader.ReadEncodedInt32();
// Guard against bad data (nextPos way too big, or nextPos not increasing)
if (nextPos >= count || nextPos < 0) {
throw new InvalidOperationException(SR.GetString(SR.InvalidSerializedData));
}
list.SetValue(DeserializeValue(reader), nextPos);
}
return list;
}
case Token_StringFormatted:
{
object result = null;
Type valueType = DeserializeType(reader);
string formattedValue = reader.ReadString();
if (valueType != null) {
TypeConverter converter = TypeDescriptor.GetConverter(valueType);
// TypeDescriptor.GetConverter() will never return null. The ref docs
// for this method are incorrect.
try {
result = converter.ConvertFromInvariantString(formattedValue);
}
catch (Exception exception) {
if (_throwOnErrorDeserializing) {
throw;
}
else {
WebBaseEvent.RaiseSystemEvent(
SR.GetString(SR.Webevent_msg_OSF_Deserialization_String, valueType.AssemblyQualifiedName),
this,
WebEventCodes.WebErrorObjectStateFormatterDeserializationError,
WebEventCodes.UndefinedEventDetailCode,
exception);
}
}
}
return result;
}
case Token_BinarySerialized:
{
int length = reader.ReadEncodedInt32();
byte[] buffer = new byte[length];
if (length != 0) {
reader.Read(buffer, 0, length);
}
object result = null;
MemoryStream ms = GetMemoryStream();
try {
ms.Write(buffer, 0, length);
ms.Position = 0;
IFormatter formatter = new BinaryFormatter();
result = formatter.Deserialize(ms);
}
catch (Exception exception) {
if (_throwOnErrorDeserializing) {
throw;
}
else {
WebBaseEvent.RaiseSystemEvent(
SR.GetString(SR.Webevent_msg_OSF_Deserialization_Binary),
this,
WebEventCodes.WebErrorObjectStateFormatterDeserializationError,
WebEventCodes.UndefinedEventDetailCode,
exception);
}
}
finally {
ReleaseMemoryStream(ms);
}
return result;
}
default:
throw new InvalidOperationException(SR.GetString(SR.InvalidSerializedData));
}
}
/// <devdoc>
/// Retrieves a MemoryStream instance.
/// </devdoc>
private static MemoryStream GetMemoryStream() {
return new MemoryStream(2048);
}
/// <devdoc>
/// Initializes this instance to perform deserialization.
/// </devdoc>
private void InitializeDeserializer() {
_typeList = new ArrayList();
for (int i = 0; i < KnownTypes.Length; i++) {
AddDeserializationTypeReference(KnownTypes[i]);
}
_stringList = new string[Byte.MaxValue];
_stringTableCount = 0;
}
/// <devdoc>
/// Initializes this instance to perform serialization.
/// </devdoc>
private void InitializeSerializer() {
_typeTable = new HybridDictionary();
for (int i = 0; i < KnownTypes.Length; i++) {
AddSerializationTypeReference(KnownTypes[i]);
}
_stringList = new string[Byte.MaxValue];
_stringTable = new Hashtable(StringComparer.Ordinal);
_stringTableCount = 0;
}
/// <devdoc>
/// Releases a MemoryStream instance.
/// </devdoc>
private static void ReleaseMemoryStream(MemoryStream stream) {
stream.Dispose();
}
/// <devdoc>
/// Serializes an object graph into a textual serialized form.
/// </devdoc>
public string Serialize(object stateGraph) {
// If the developer called Serialize() manually on an ObjectStateFormatter object that was configured
// for cryptographic operations, he wouldn't have been able to specify a Purpose. We'll just provide
// a default value for him.
return Serialize(stateGraph, Purpose.User_ObjectStateFormatter_Serialize);
}
private string Serialize(object stateGraph, Purpose purpose) {
string result = null;
MemoryStream ms = GetMemoryStream();
try {
Serialize(ms, stateGraph);
ms.SetLength(ms.Position);
byte[] buffer = ms.GetBuffer();
int length = (int)ms.Length;
#if !FEATURE_PAL // FEATURE_PAL does not enable cryptography
// We only support serialization of encrypted or encoded data through our internal Page constructors
if (AspNetCryptoServiceProvider.Instance.IsDefaultProvider && !_forceLegacyCryptography) {
// If we're configured to use the new crypto providers, call into them if encryption or signing (or both) is requested.
if (_page != null && (_page.RequiresViewStateEncryptionInternal || _page.EnableViewStateMac)) {
Purpose derivedPurpose = purpose.AppendSpecificPurposes(GetSpecificPurposes());
ICryptoService cryptoService = AspNetCryptoServiceProvider.Instance.GetCryptoService(derivedPurpose);
byte[] protectedData = cryptoService.Protect(ms.ToArray());
buffer = protectedData;
length = protectedData.Length;
}
}
else {
// Otherwise go through legacy crypto mechanisms
#pragma warning disable 618 // calling obsolete methods
if (_page != null && _page.RequiresViewStateEncryptionInternal) {
buffer = MachineKeySection.EncryptOrDecryptData(true, buffer, GetMacKeyModifier(), 0, length);
length = buffer.Length;
}
// We need to encode if the page has EnableViewStateMac or we got passed in some mac key string
else if ((_page != null && _page.EnableViewStateMac) || _macKeyBytes != null) {
buffer = MachineKeySection.GetEncodedData(buffer, GetMacKeyModifier(), 0, ref length);
}
#pragma warning restore 618 // calling obsolete methods
}
#endif // !FEATURE_PAL
result = Convert.ToBase64String(buffer, 0, length);
}
finally {
ReleaseMemoryStream(ms);
}
return result;
}
[SecurityPermission(SecurityAction.Assert, Flags = SecurityPermissionFlag.SerializationFormatter)]
internal void SerializeWithAssert(Stream outputStream, object stateGraph) {
Serialize(outputStream, stateGraph);
}
/// <devdoc>
/// Serializes an object graph into a binary serialized form within
/// the specified stream.
/// </devdoc>
public void Serialize(Stream outputStream, object stateGraph) {
if (outputStream == null) {
throw new ArgumentNullException("outputStream");
}
InitializeSerializer();
SerializerBinaryWriter writer = new SerializerBinaryWriter(outputStream);
writer.Write(Marker_Format);
writer.Write(Marker_Version_1);
SerializeValue(writer, stateGraph);
}
/// <devdoc>
/// Serializes an IndexedString. If this is the first occurrence, it is written
/// out to the underlying stream, and is added to the string table for future
/// reference. Otherwise, a reference by index is written out.
/// </devdoc>
private void SerializeIndexedString(SerializerBinaryWriter writer, string s) {
object id = _stringTable[s];
if (id != null) {
writer.Write(Token_IndexedString);
writer.Write((byte)(int)id);
return;
}
AddSerializationStringReference(s);
writer.Write(Token_IndexedStringAdd);
writer.Write(s);
}
/// <devdoc>
/// Serializes a Type. If this is the first occurrence, the type name is written
/// out to the underlying stream, and the type is added to the string table for future
/// reference. Otherwise, a reference by index is written out.
/// </devdoc>
private void SerializeType(SerializerBinaryWriter writer, Type type) {
object id = _typeTable[type];
if (id != null) {
writer.Write(Token_TypeRef);
writer.WriteEncoded((int)id);
return;
}
AddSerializationTypeReference(type);
if (type.Assembly == HttpContext.SystemWebAssembly) {
writer.Write(Token_TypeRefAddLocal);
writer.Write(type.FullName);
}
else {
writer.Write(Token_TypeRefAdd);
writer.Write(type.AssemblyQualifiedName);
}
}
/// <devdoc>
/// Serializes a single value using the specified writer.
/// Handles exceptions to provide more information about the value being serialized.
/// </devdoc>
private void SerializeValue(SerializerBinaryWriter writer, object value) {
try {
Stack objectStack = new Stack();
objectStack.Push(value);
do {
value = objectStack.Pop();
if (value == null) {
writer.Write(Token_Null);
continue;
}
// NOTE: These are ordered roughly in the order of frequency.
if (value is string) {
string s = (string)value;
if (s.Length == 0) {
writer.Write(Token_EmptyString);
}
else {
writer.Write(Token_String);
writer.Write(s);
}
continue;
}
if (value is int) {
int i = (int)value;
if (i == 0) {
writer.Write(Token_ZeroInt32);
}
else {
writer.Write(Token_Int32);
writer.WriteEncoded(i);
}
continue;
}
if (value is Pair) {
writer.Write(Token_Pair);
Pair p = (Pair)value;
objectStack.Push(p.Second);
objectStack.Push(p.First);
continue;
}
if (value is Triplet) {
writer.Write(Token_Triplet);
Triplet t = (Triplet)value;
objectStack.Push(t.Third);
objectStack.Push(t.Second);
objectStack.Push(t.First);
continue;
}
if (value is IndexedString) {
Debug.Assert(((IndexedString)value).Value != null);
SerializeIndexedString(writer, ((IndexedString)value).Value);
continue;
}
if (value.GetType() == typeof(ArrayList)) {
writer.Write(Token_ArrayList);
ArrayList list = (ArrayList)value;
writer.WriteEncoded(list.Count);
for (int i = list.Count - 1; i >= 0; i--) {
objectStack.Push(list[i]);
}
continue;
}
if (value is bool) {
if (((bool)value)) {
writer.Write(Token_True);
}
else {
writer.Write(Token_False);
}
continue;
}
if (value is byte) {
writer.Write(Token_Byte);
writer.Write((byte)value);
continue;
}
if (value is char) {
writer.Write(Token_Char);
writer.Write((char)value);
continue;
}
if (value is DateTime) {
writer.Write(Token_DateTime);
writer.Write(((DateTime)value).ToBinary());
continue;
}
if (value is double) {
writer.Write(Token_Double);
writer.Write((double)value);
continue;
}
if (value is short) {
writer.Write(Token_Int16);
writer.Write((short)value);
continue;
}
if (value is float) {
writer.Write(Token_Single);
writer.Write((float)value);
continue;
}
if (value is IDictionary) {
bool canSerializeDictionary = false;
if (value.GetType() == typeof(Hashtable)) {
writer.Write(Token_Hashtable);
canSerializeDictionary = true;
}
else if (value.GetType() == typeof(HybridDictionary)) {
writer.Write(Token_HybridDictionary);
canSerializeDictionary = true;
}
if (canSerializeDictionary) {
IDictionary table = (IDictionary)value;
writer.WriteEncoded(table.Count);
if (table.Count != 0) {
foreach (DictionaryEntry entry in table) {
objectStack.Push(entry.Value);
objectStack.Push(entry.Key);
}
}
continue;
}
}
if (value is EventValidationStore) {
writer.Write(Token_EventValidationStore);
((EventValidationStore)value).SerializeTo(writer.BaseStream);
continue;
}
if (value is Type) {
writer.Write(Token_Type);
SerializeType(writer, (Type)value);
continue;
}
Type valueType = value.GetType();
if (value is Array) {
// We only support Arrays with rank 1 (No multi dimensional arrays
if (((Array)value).Rank > 1) {
continue;
}
Type underlyingType = valueType.GetElementType();
if (underlyingType == typeof(string)) {
string[] strings = (string[])value;
bool containsNulls = false;
for (int i = 0; i < strings.Length; i++) {
if (strings[i] == null) {
// Will have to treat these as generic arrays since we
// can't represent nulls in the binary stream, without
// writing out string token markers.
// Generic array writing includes the token markers.
containsNulls = true;
break;
}
}
if (!containsNulls) {
writer.Write(Token_StringArray);
writer.WriteEncoded(strings.Length);
for (int i = 0; i < strings.Length; i++) {
writer.Write(strings[i]);
}
continue;
}
}
Array values = (Array)value;
// Optimize for sparse arrays, if the array is more than 3/4 nulls
if (values.Length > 3) {
int sparseThreshold = (values.Length / 4) + 1;
int numValues = 0;
List<int> items = new List<int>(sparseThreshold);
for (int i = 0; i < values.Length; ++i) {
if (values.GetValue(i) != null) {
++numValues;
if (numValues >= sparseThreshold) {
break;
}
items.Add(i);
}
}
// We have enough nulls to use sparse array format <index, value, index, value, ...>
if (numValues < sparseThreshold) {
writer.Write(Token_SparseArray);
SerializeType(writer, underlyingType);
writer.WriteEncoded(values.Length);
writer.WriteEncoded(numValues);
// Now we need to just serialize pairs representing the index, and the item
foreach (int index in items) {
writer.WriteEncoded(index);
SerializeValue(writer, values.GetValue(index));
}
continue;
}
}
writer.Write(Token_Array);
SerializeType(writer, underlyingType);
writer.WriteEncoded(values.Length);
for (int i = values.Length - 1; i >= 0; i--) {
objectStack.Push(values.GetValue(i));
}
continue;
}
if (valueType.IsEnum) {
Type underlyingType = Enum.GetUnderlyingType(valueType);
if (underlyingType == typeof(int)) {
writer.Write(Token_IntEnum);
SerializeType(writer, valueType);
writer.WriteEncoded((int)value);
continue;
}
}
if (valueType == typeof(Color)) {
Color c = (Color)value;
if (c.IsEmpty) {
writer.Write(Token_EmptyColor);
continue;
}
if (!c.IsNamedColor) {
writer.Write(Token_Color);
writer.Write(c.ToArgb());
continue;
}
else {
writer.Write(Token_KnownColor);
writer.WriteEncoded((int)c.ToKnownColor());
continue;
}
}
if (value is Unit) {
Unit uval = (Unit)value;
if (uval.IsEmpty) {
writer.Write(Token_EmptyUnit);
}
else {
writer.Write(Token_Unit);
writer.Write(uval.Value);
writer.Write((int)uval.Type);
}
continue;
}
// Handle the remaining types
// First try to get a type converter, and then resort to
// binary serialization if all else fails
TypeConverter converter = TypeDescriptor.GetConverter(valueType);
bool canConvert = System.Web.UI.Util.CanConvertToFrom(converter, typeof(string));
if (canConvert) {
writer.Write(Token_StringFormatted);
SerializeType(writer, valueType);
writer.Write(converter.ConvertToInvariantString(null, value));
}
else {
IFormatter formatter = new BinaryFormatter();
MemoryStream ms = new MemoryStream(256);
formatter.Serialize(ms, value);
byte[] buffer = ms.GetBuffer();
int length = (int)ms.Length;
writer.Write(Token_BinarySerialized);
writer.WriteEncoded(length);
if (buffer.Length != 0) {
writer.Write(buffer, 0, (int)length);
}
}
}
while (objectStack.Count > 0);
}
catch (Exception serializationException) {
if (value != null)
throw new ArgumentException(SR.GetString(SR.ErrorSerializingValue, value.ToString(), value.GetType().FullName),
serializationException);
throw serializationException;
}
}
#region Implementation of IStateFormatter
object IStateFormatter.Deserialize(string serializedState) {
return Deserialize(serializedState);
}
string IStateFormatter.Serialize(object state) {
return Serialize(state);
}
#endregion
#region Implementation of IFormatter
/// <internalonly/>
SerializationBinder IFormatter.Binder {
get {
return null;
}
set {
}
}
/// <internalonly/>
StreamingContext IFormatter.Context {
get {
return new StreamingContext(StreamingContextStates.All);
}
set {
}
}
/// <internalonly/>
ISurrogateSelector IFormatter.SurrogateSelector {
get {
return null;
}
set {
}
}
/// <internalonly/>
object IFormatter.Deserialize(Stream serializationStream) {
return Deserialize(serializationStream);
}
/// <internalonly/>
void IFormatter.Serialize(Stream serializationStream, object stateGraph) {
Serialize(serializationStream, stateGraph);
}
#endregion
#region IStateFormatter2 Members
object IStateFormatter2.Deserialize(string serializedState, Purpose purpose) {
return Deserialize(serializedState, purpose);
}
string IStateFormatter2.Serialize(object state, Purpose purpose) {
return Serialize(state, purpose);
}
#endregion
/// <devdoc>
/// Custom BinaryReader used during the deserialization.
/// </devdoc>
private sealed class SerializerBinaryReader : BinaryReader {
public SerializerBinaryReader(Stream stream) : base(stream) {
}
public int ReadEncodedInt32() {
return Read7BitEncodedInt();
}
}
/// <devdoc>
/// Custom BinaryWriter used during the serialization.
/// </devdoc>
private sealed class SerializerBinaryWriter : BinaryWriter {
public SerializerBinaryWriter(Stream stream) : base(stream) {
}
public void WriteEncoded(int value) {
//
uint v = (uint)value;
while (v >= 0x80) {
Write((byte)(v | 0x80));
v >>= 7;
}
Write((byte)v);
}
}
}
}
|