e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
1300 lines
52 KiB
C#
1300 lines
52 KiB
C#
//------------------------------------------------------------------------------
|
|
// <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 [....]
|
|
// 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 [....]
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|