1 //------------------------------------------------------------------------------
2 // <copyright file="ObjectStateFormatter.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
7 namespace System
.Web
.UI
{
10 using System
.Collections
;
11 using System
.Collections
.Generic
;
12 using System
.Collections
.Specialized
;
13 using System
.ComponentModel
;
16 using System
.Globalization
;
17 using System
.Reflection
;
18 using System
.Runtime
.Serialization
;
19 using System
.Runtime
.Serialization
.Formatters
.Binary
;
20 using System
.Security
;
21 using System
.Security
.Permissions
;
23 using System
.Web
.Compilation
;
24 using System
.Web
.Configuration
;
25 using System
.Web
.Util
;
26 using System
.Web
.Management
;
27 using System
.Web
.UI
.WebControls
;
28 using System
.Web
.Security
.Cryptography
;
35 /// ObjectStateFormatter is designed to efficiently serialize arbitrary object graphs
36 /// that represent the state of an object (decomposed into simpler types) into
37 /// a highly compact binary or ASCII representations.
38 /// The formatter contains native support for optimized serialization of a fixed
39 /// set of known types such as ints, shorts, booleans, strings, other primitive types
40 /// arrays, Pairs, Triplets, ArrayLists, Hashtables etc. In addition it utilizes
41 /// TypeConverters for semi-optimized serialization of custom types. Finally, it uses
42 /// binary serialization as a fallback mechanism. The formatter is also able to compress
43 /// IndexedStrings contained in the object graph.
45 public sealed class ObjectStateFormatter
: IStateFormatter
, IStateFormatter2
, IFormatter
{
47 // Optimized type tokens
48 private const byte Token_Int16
= 1;
49 private const byte Token_Int32
= 2;
50 private const byte Token_Byte
= 3;
51 private const byte Token_Char
= 4;
52 private const byte Token_String
= 5;
53 private const byte Token_DateTime
= 6;
54 private const byte Token_Double
= 7;
55 private const byte Token_Single
= 8;
56 private const byte Token_Color
= 9;
57 private const byte Token_KnownColor
= 10;
58 private const byte Token_IntEnum
= 11;
59 private const byte Token_EmptyColor
= 12;
60 private const byte Token_Pair
= 15;
61 private const byte Token_Triplet
= 16;
62 private const byte Token_Array
= 20;
63 private const byte Token_StringArray
= 21;
64 private const byte Token_ArrayList
= 22;
65 private const byte Token_Hashtable
= 23;
66 private const byte Token_HybridDictionary
= 24;
67 private const byte Token_Type
= 25;
68 // private const byte Token_Nullable = 26; Removed per DevDiv 165426
69 // Background: Used to support nullables as a special case, CLR added support for this
70 // but they forgot to remove the deserialization code when they removed the support
71 // potentially Beta2 customers could have serialized data (WebParts) which have this token.
72 // We removed support since this was broken anyways in RTM.
73 private const byte Token_Unit
= 27;
74 private const byte Token_EmptyUnit
= 28;
75 private const byte Token_EventValidationStore
= 29;
77 // String-table optimized strings
78 private const byte Token_IndexedStringAdd
= 30;
79 private const byte Token_IndexedString
= 31;
81 // Semi-optimized (TypeConverter-based)
82 private const byte Token_StringFormatted
= 40;
84 // Semi-optimized (Types)
85 private const byte Token_TypeRefAdd
= 41;
86 private const byte Token_TypeRefAddLocal
= 42;
87 private const byte Token_TypeRef
= 43;
89 // Un-optimized (Binary serialized) types
90 private const byte Token_BinarySerialized
= 50;
92 // Optimized for sparse arrays
93 private const byte Token_SparseArray
= 60;
96 private const byte Token_Null
= 100;
97 private const byte Token_EmptyString
= 101;
98 private const byte Token_ZeroInt32
= 102;
99 private const byte Token_True
= 103;
100 private const byte Token_False
= 104;
102 // Known types for which we generate short type references
103 // rather than assembly qualified names
107 private static readonly Type
[] KnownTypes
=
115 // Format and Version
116 private const byte Marker_Format
= 0xFF;
117 private const byte Marker_Version_1
= 0x01;
119 // The size of the string table. At most it can be Byte.MaxValue.
121 private const int StringTableSize
= Byte
.MaxValue
;
123 // Used during serialization
124 private IDictionary _typeTable
;
125 private IDictionary _stringTable
;
127 // Used during deserialization
128 private IList _typeList
;
130 // Used during both serialization and deserialization
131 private int _stringTableCount
;
132 private string[] _stringList
;
134 // Used for performing Mac-encoding when this LosSerializer is used
135 // in view state serialization.
136 private byte[] _macKeyBytes
;
137 private readonly bool _forceLegacyCryptography
;
139 // Combined with Purpose objects which are passed in during serialization / deserialization.
140 private List
<string> _specificPurposes
;
142 // If true, this class will throw an exception if it cannot deserialize a type or value.
143 // If false, this class will use insert "null" if it cannot deserialize a type or value.
144 // Default is true, WebParts Personalization sets this to false.
145 private bool _throwOnErrorDeserializing
;
147 // We use page to determine whether to to encrypt or decrypt based on Page.RequiresViewStateEncryptionInternal or Page.ContainsEncryptedViewstate
151 /// Initializes a new instance of the ObjectStateFormatter.
153 public ObjectStateFormatter() : this(null) {
158 /// Initializes a new instance of the ObjectStateFormatter. A MAC encoding
159 /// key can be specified to have the serialized data encoded for view state
161 /// NOTE: this constructor is mainly for LOSFormatter's consumption, not used internally
163 internal ObjectStateFormatter(byte[] macEncodingKey
) : this(null, true) {
164 _macKeyBytes
= macEncodingKey
;
165 if (macEncodingKey
!= null) {
166 // If the developer explicitly asked for the data to be signed, we must honor that.
167 _forceLegacyCryptography
= true;
173 /// Initializes a new instance of the ObjectStateFormatter. A MAC encoding
174 /// key can be specified to have the serialized data encoded for view state
175 /// purposes. The Page object is used to determine whether the viewstate will be encrypted
176 /// for serialize and deserialize.
179 internal ObjectStateFormatter(Page page
, bool throwOnErrorDeserializing
) {
181 _throwOnErrorDeserializing
= throwOnErrorDeserializing
;
184 // This will return a list of specific purposes (for cryptographic subkey generation).
185 internal List
<string> GetSpecificPurposes() {
186 if (_specificPurposes
== null) {
187 // Only generate a specific purpose list if we have a Page
192 // Note: duplicated (somewhat) in GetMacKeyModifier, keep in sync
193 // See that method for comments on why these modifiers are in place
195 List
<string> specificPurposes
= new List
<string>() {
196 "TemplateSourceDirectory: " + _page
.TemplateSourceDirectory
.ToUpperInvariant(),
197 "Type: " + _page
.GetType().Name
.ToUpperInvariant()
200 if (_page
.ViewStateUserKey
!= null) {
201 specificPurposes
.Add("ViewStateUserKey: " + _page
.ViewStateUserKey
);
204 _specificPurposes
= specificPurposes
;
207 return _specificPurposes
;
210 // This will return the MacKeyModifier provided in the LOSFormatter constructor or
211 // generate one from Page if EnableViewStateMac is true.
212 private byte[] GetMacKeyModifier() {
213 if (_macKeyBytes
== null) {
214 // Only generate a MacKeyModifier if we have a page
219 // Note: duplicated (somewhat) in GetSpecificPurposes, keep in sync
221 // Use the page's directory and class name as part of the key (ASURT 64044)
222 uint pageHashCode
= _page
.GetClientStateIdentifier();
224 string viewStateUserKey
= _page
.ViewStateUserKey
;
225 if (viewStateUserKey
!= null) {
226 // Modify the key with the ViewStateUserKey, if any (ASURT 126375)
227 int count
= Encoding
.Unicode
.GetByteCount(viewStateUserKey
);
228 _macKeyBytes
= new byte[count
+ 4];
229 Encoding
.Unicode
.GetBytes(viewStateUserKey
, 0, viewStateUserKey
.Length
, _macKeyBytes
, 4);
233 _macKeyBytes
= new byte[4];
236 _macKeyBytes
[0] = (byte)pageHashCode
;
237 _macKeyBytes
[1] = (byte)(pageHashCode
>> 8);
238 _macKeyBytes
[2] = (byte)(pageHashCode
>> 16);
239 _macKeyBytes
[3] = (byte)(pageHashCode
>> 24);
245 /// Adds a string reference during the deserialization process
246 /// to support deserialization of IndexedStrings.
247 /// The string is added to the string list on the fly, so it is available
248 /// for future reference by index.
250 private void AddDeserializationStringReference(string s
) {
251 Debug
.Assert((s
!= null) && (s
.Length
!= 0));
253 if (_stringTableCount
== StringTableSize
) {
254 // loop around to the start of the table
255 _stringTableCount
= 0;
258 _stringList
[_stringTableCount
] = s
;
263 /// Adds a type reference during the deserialization process,
264 /// so that it can be referred to later by its index.
266 private void AddDeserializationTypeReference(Type type
) {
267 // Type may be null, if there is no longer a Type on the system with the saved type name.
268 // This is unlikely to happen with a Type stored in ViewState, but more likely with a Type
269 // stored in Personalization.
274 /// Adds a string reference during the serialization process to support
275 /// the serialization of IndexedStrings.
276 /// The string is added to the string list, as well as to a string table
277 /// for quick lookup.
279 private void AddSerializationStringReference(string s
) {
280 Debug
.Assert((s
!= null) && (s
.Length
!= 0));
282 if (_stringTableCount
== StringTableSize
) {
283 // loop around to the start of the table
284 _stringTableCount
= 0;
287 string oldString
= _stringList
[_stringTableCount
];
288 if (oldString
!= null) {
289 // it means we're looping around, and the existing table entry
290 // needs to be removed, as a new one will replace it
291 Debug
.Assert(_stringTable
.Contains(oldString
));
292 _stringTable
.Remove(oldString
);
295 _stringTable
[s
] = _stringTableCount
;
296 _stringList
[_stringTableCount
] = s
;
301 /// Adds a type reference during the serialization process, so it
302 /// can be later referred to by its index.
304 private void AddSerializationTypeReference(Type type
) {
305 Debug
.Assert(type
!= null);
307 int typeID
= _typeTable
.Count
;
308 _typeTable
[type
] = typeID
;
311 [SecurityPermission(SecurityAction
.Assert
, Flags
= SecurityPermissionFlag
.SerializationFormatter
)]
312 internal object DeserializeWithAssert(Stream inputStream
) {
313 return Deserialize(inputStream
);
317 /// Deserializes an object graph from its binary serialized form
318 /// contained in the specified stream.
320 public object Deserialize(Stream inputStream
) {
321 if (inputStream
== null) {
322 throw new ArgumentNullException("inputStream");
325 Exception deserializationException
= null;
327 InitializeDeserializer();
329 SerializerBinaryReader reader
= new SerializerBinaryReader(inputStream
);
331 byte formatMarker
= reader
.ReadByte();
333 if (formatMarker
== Marker_Format
) {
334 byte versionMarker
= reader
.ReadByte();
336 Debug
.Assert(versionMarker
== Marker_Version_1
);
337 if (versionMarker
== Marker_Version_1
) {
338 return DeserializeValue(reader
);
342 catch (Exception e
) {
343 deserializationException
= e
;
346 // throw an exception if there was an exception during deserialization
347 // or if deserialization was skipped because of invalid format or
348 // version data in the stream
350 throw new ArgumentException(SR
.GetString(SR
.InvalidSerializedData
), deserializationException
);
355 /// Deserializes an object graph from its textual serialized form
356 /// contained in the specified string.
358 public object Deserialize(string inputString
) {
359 // If the developer called Deserialize() manually on an ObjectStateFormatter object that was configured
360 // for cryptographic operations, he wouldn't have been able to specify a Purpose. We'll just provide
361 // a default value for him.
362 return Deserialize(inputString
, Purpose
.User_ObjectStateFormatter_Serialize
);
365 private object Deserialize(string inputString
, Purpose purpose
) {
366 if (String
.IsNullOrEmpty(inputString
)) {
367 throw new ArgumentNullException("inputString");
370 byte[] inputBytes
= Convert
.FromBase64String(inputString
);
371 int length
= inputBytes
.Length
;
373 #if !FEATURE_PAL // FEATURE_PAL does not enable cryptography
375 if (AspNetCryptoServiceProvider
.Instance
.IsDefaultProvider
&& !_forceLegacyCryptography
) {
376 // If we're configured to use the new crypto providers, call into them if encryption or signing (or both) is requested.
378 if (_page
!= null && (_page
.ContainsEncryptedViewState
|| _page
.EnableViewStateMac
)) {
379 Purpose derivedPurpose
= purpose
.AppendSpecificPurposes(GetSpecificPurposes());
380 ICryptoService cryptoService
= AspNetCryptoServiceProvider
.Instance
.GetCryptoService(derivedPurpose
);
381 byte[] clearData
= cryptoService
.Unprotect(inputBytes
);
382 inputBytes
= clearData
;
383 length
= clearData
.Length
;
387 // Otherwise go through legacy crypto mechanisms
388 #pragma warning disable 618 // calling obsolete methods
389 if (_page
!= null && _page
.ContainsEncryptedViewState
) {
390 inputBytes
= MachineKeySection
.EncryptOrDecryptData(false, inputBytes
, GetMacKeyModifier(), 0, length
);
391 length
= inputBytes
.Length
;
393 // We need to decode if the page has EnableViewStateMac or we got passed in some mac key string
394 else if ((_page
!= null && _page
.EnableViewStateMac
) || _macKeyBytes
!= null) {
395 inputBytes
= MachineKeySection
.GetDecodedData(inputBytes
, GetMacKeyModifier(), 0, length
, ref length
);
397 #pragma warning restore 618 // calling obsolete methods
401 // MSRC 10405: Don't propagate inner exceptions, as they may contain sensitive cryptographic information.
402 PerfCounters
.IncrementCounter(AppPerfCounter
.VIEWSTATE_MAC_FAIL
);
403 ViewStateException
.ThrowMacValidationError(null, inputString
);
405 #endif // !FEATURE_PAL
406 object result
= null;
407 MemoryStream objectStream
= GetMemoryStream();
409 objectStream
.Write(inputBytes
, 0, length
);
410 objectStream
.Position
= 0;
411 result
= Deserialize(objectStream
);
414 ReleaseMemoryStream(objectStream
);
420 /// Deserializes an IndexedString. An IndexedString can either be the string itself (the
421 /// first occurrence), or a reference to it by index into the string table.
423 private IndexedString
DeserializeIndexedString(SerializerBinaryReader reader
, byte token
) {
424 Debug
.Assert((token
== Token_IndexedStringAdd
) || (token
== Token_IndexedString
));
426 if (token
== Token_IndexedString
) {
427 // reference to string in the current string table
428 int tableIndex
= (int)reader
.ReadByte();
430 Debug
.Assert(_stringList
[tableIndex
] != null);
431 return new IndexedString(_stringList
[tableIndex
]);
434 // first occurrence of this indexed string. Read in the string, and add
435 // a reference to it, so future references can be resolved.
436 string s
= reader
.ReadString();
438 AddDeserializationStringReference(s
);
439 return new IndexedString(s
);
444 /// Deserializes a Type. A Type can either be its name (the first occurrence),
445 /// or a reference to it by index into the type table. If we cannot load the type,
446 /// we throw an exception if _throwOnErrorDeserializing is true, and we return null if
447 /// _throwOnErrorDeserializing is false.
449 private Type
DeserializeType(SerializerBinaryReader reader
) {
450 byte token
= reader
.ReadByte();
451 Debug
.Assert((token
== Token_TypeRef
) ||
452 (token
== Token_TypeRefAdd
) ||
453 (token
== Token_TypeRefAddLocal
));
455 if (token
== Token_TypeRef
) {
456 // reference by index into type table
457 int typeID
= reader
.ReadEncodedInt32();
458 return (Type
)_typeList
[typeID
];
461 // first occurrence of this type. Read in the type, resolve it, and
462 // add it to the type table
463 string typeName
= reader
.ReadString();
465 Type resolvedType
= null;
467 if (token
== Token_TypeRefAddLocal
) {
468 resolvedType
= HttpContext
.SystemWebAssembly
.GetType(typeName
, true);
471 resolvedType
= Type
.GetType(typeName
, true);
474 catch (Exception exception
) {
475 if (_throwOnErrorDeserializing
) {
480 WebBaseEvent
.RaiseSystemEvent(
481 SR
.GetString(SR
.Webevent_msg_OSF_Deserialization_Type
, typeName
),
483 WebEventCodes
.WebErrorObjectStateFormatterDeserializationError
,
484 WebEventCodes
.UndefinedEventDetailCode
,
489 AddDeserializationTypeReference(resolvedType
);
495 /// Deserializes a single value from the underlying stream.
496 /// Essentially a token is read, followed by as much data needed to recreate
497 /// the single value.
499 private object DeserializeValue(SerializerBinaryReader reader
) {
500 byte token
= reader
.ReadByte();
502 // NOTE: Preserve the order here with the order of the logic in
503 // the SerializeValue method.
508 case Token_EmptyString
:
511 return reader
.ReadString();
512 case Token_ZeroInt32
:
515 return reader
.ReadEncodedInt32();
517 return new Pair(DeserializeValue(reader
),
518 DeserializeValue(reader
));
520 return new Triplet(DeserializeValue(reader
),
521 DeserializeValue(reader
),
522 DeserializeValue(reader
));
523 case Token_IndexedString
:
524 case Token_IndexedStringAdd
:
525 return DeserializeIndexedString(reader
, token
);
526 case Token_ArrayList
:
528 int count
= reader
.ReadEncodedInt32();
529 ArrayList list
= new ArrayList(count
);
530 for (int i
= 0; i
< count
; i
++) {
531 list
.Add(DeserializeValue(reader
));
541 return reader
.ReadByte();
543 return reader
.ReadChar();
545 return DateTime
.FromBinary(reader
.ReadInt64());
547 return reader
.ReadDouble();
549 return reader
.ReadInt16();
551 return reader
.ReadSingle();
552 case Token_Hashtable
:
553 case Token_HybridDictionary
:
555 int count
= reader
.ReadEncodedInt32();
558 if (token
== Token_Hashtable
) {
559 table
= new Hashtable(count
);
562 table
= new HybridDictionary(count
);
564 for (int i
= 0; i
< count
; i
++) {
565 table
.Add(DeserializeValue(reader
),
566 DeserializeValue(reader
));
572 return DeserializeType(reader
);
573 case Token_StringArray
:
575 int count
= reader
.ReadEncodedInt32();
577 string[] array
= new string[count
];
578 for (int i
= 0; i
< count
; i
++) {
579 array
[i
] = reader
.ReadString();
586 Type elementType
= DeserializeType(reader
);
587 int count
= reader
.ReadEncodedInt32();
589 Array list
= Array
.CreateInstance(elementType
, count
);
590 for (int i
= 0; i
< count
; i
++) {
591 list
.SetValue(DeserializeValue(reader
), i
);
598 Type enumType
= DeserializeType(reader
);
599 int enumValue
= reader
.ReadEncodedInt32();
601 return Enum
.ToObject(enumType
, enumValue
);
604 return Color
.FromArgb(reader
.ReadInt32());
605 case Token_EmptyColor
:
607 case Token_KnownColor
:
608 return Color
.FromKnownColor((KnownColor
)reader
.ReadEncodedInt32());
610 return new Unit(reader
.ReadDouble(), (UnitType
)reader
.ReadInt32());
611 case Token_EmptyUnit
:
613 case Token_EventValidationStore
:
614 return EventValidationStore
.DeserializeFrom(reader
.BaseStream
);
615 case Token_SparseArray
:
617 Type elementType
= DeserializeType(reader
);
618 int count
= reader
.ReadEncodedInt32();
619 int itemCount
= reader
.ReadEncodedInt32();
621 // Guard against bad data
622 if (itemCount
> count
) {
623 throw new InvalidOperationException(SR
.GetString(SR
.InvalidSerializedData
));
626 Array list
= Array
.CreateInstance(elementType
, count
);
627 for (int i
= 0; i
< itemCount
; ++i
) {
628 // Data is encoded as <index, Item>
629 int nextPos
= reader
.ReadEncodedInt32();
631 // Guard against bad data (nextPos way too big, or nextPos not increasing)
632 if (nextPos
>= count
|| nextPos
< 0) {
633 throw new InvalidOperationException(SR
.GetString(SR
.InvalidSerializedData
));
635 list
.SetValue(DeserializeValue(reader
), nextPos
);
640 case Token_StringFormatted
:
642 object result
= null;
644 Type valueType
= DeserializeType(reader
);
645 string formattedValue
= reader
.ReadString();
647 if (valueType
!= null) {
648 TypeConverter converter
= TypeDescriptor
.GetConverter(valueType
);
649 // TypeDescriptor.GetConverter() will never return null. The ref docs
650 // for this method are incorrect.
652 result
= converter
.ConvertFromInvariantString(formattedValue
);
654 catch (Exception exception
) {
655 if (_throwOnErrorDeserializing
) {
659 WebBaseEvent
.RaiseSystemEvent(
660 SR
.GetString(SR
.Webevent_msg_OSF_Deserialization_String
, valueType
.AssemblyQualifiedName
),
662 WebEventCodes
.WebErrorObjectStateFormatterDeserializationError
,
663 WebEventCodes
.UndefinedEventDetailCode
,
671 case Token_BinarySerialized
:
673 int length
= reader
.ReadEncodedInt32();
675 byte[] buffer
= new byte[length
];
677 reader
.Read(buffer
, 0, length
);
680 object result
= null;
681 MemoryStream ms
= GetMemoryStream();
683 ms
.Write(buffer
, 0, length
);
685 IFormatter formatter
= new BinaryFormatter();
687 result
= formatter
.Deserialize(ms
);
689 catch (Exception exception
) {
690 if (_throwOnErrorDeserializing
) {
694 WebBaseEvent
.RaiseSystemEvent(
695 SR
.GetString(SR
.Webevent_msg_OSF_Deserialization_Binary
),
697 WebEventCodes
.WebErrorObjectStateFormatterDeserializationError
,
698 WebEventCodes
.UndefinedEventDetailCode
,
703 ReleaseMemoryStream(ms
);
708 throw new InvalidOperationException(SR
.GetString(SR
.InvalidSerializedData
));
713 /// Retrieves a MemoryStream instance.
715 private static MemoryStream
GetMemoryStream() {
716 return new MemoryStream(2048);
721 /// Initializes this instance to perform deserialization.
723 private void InitializeDeserializer() {
724 _typeList
= new ArrayList();
726 for (int i
= 0; i
< KnownTypes
.Length
; i
++) {
727 AddDeserializationTypeReference(KnownTypes
[i
]);
730 _stringList
= new string[Byte
.MaxValue
];
731 _stringTableCount
= 0;
735 /// Initializes this instance to perform serialization.
737 private void InitializeSerializer() {
738 _typeTable
= new HybridDictionary();
740 for (int i
= 0; i
< KnownTypes
.Length
; i
++) {
741 AddSerializationTypeReference(KnownTypes
[i
]);
744 _stringList
= new string[Byte
.MaxValue
];
745 _stringTable
= new Hashtable(StringComparer
.Ordinal
);
746 _stringTableCount
= 0;
750 /// Releases a MemoryStream instance.
752 private static void ReleaseMemoryStream(MemoryStream stream
) {
757 /// Serializes an object graph into a textual serialized form.
759 public string Serialize(object stateGraph
) {
760 // If the developer called Serialize() manually on an ObjectStateFormatter object that was configured
761 // for cryptographic operations, he wouldn't have been able to specify a Purpose. We'll just provide
762 // a default value for him.
763 return Serialize(stateGraph
, Purpose
.User_ObjectStateFormatter_Serialize
);
766 private string Serialize(object stateGraph
, Purpose purpose
) {
767 string result
= null;
769 MemoryStream ms
= GetMemoryStream();
771 Serialize(ms
, stateGraph
);
772 ms
.SetLength(ms
.Position
);
774 byte[] buffer
= ms
.GetBuffer();
775 int length
= (int)ms
.Length
;
777 #if !FEATURE_PAL // FEATURE_PAL does not enable cryptography
778 // We only support serialization of encrypted or encoded data through our internal Page constructors
780 if (AspNetCryptoServiceProvider
.Instance
.IsDefaultProvider
&& !_forceLegacyCryptography
) {
781 // If we're configured to use the new crypto providers, call into them if encryption or signing (or both) is requested.
783 if (_page
!= null && (_page
.RequiresViewStateEncryptionInternal
|| _page
.EnableViewStateMac
)) {
784 Purpose derivedPurpose
= purpose
.AppendSpecificPurposes(GetSpecificPurposes());
785 ICryptoService cryptoService
= AspNetCryptoServiceProvider
.Instance
.GetCryptoService(derivedPurpose
);
786 byte[] protectedData
= cryptoService
.Protect(ms
.ToArray());
787 buffer
= protectedData
;
788 length
= protectedData
.Length
;
792 // Otherwise go through legacy crypto mechanisms
793 #pragma warning disable 618 // calling obsolete methods
794 if (_page
!= null && _page
.RequiresViewStateEncryptionInternal
) {
795 buffer
= MachineKeySection
.EncryptOrDecryptData(true, buffer
, GetMacKeyModifier(), 0, length
);
796 length
= buffer
.Length
;
798 // We need to encode if the page has EnableViewStateMac or we got passed in some mac key string
799 else if ((_page
!= null && _page
.EnableViewStateMac
) || _macKeyBytes
!= null) {
800 buffer
= MachineKeySection
.GetEncodedData(buffer
, GetMacKeyModifier(), 0, ref length
);
802 #pragma warning restore 618 // calling obsolete methods
805 #endif // !FEATURE_PAL
806 result
= Convert
.ToBase64String(buffer
, 0, length
);
809 ReleaseMemoryStream(ms
);
814 [SecurityPermission(SecurityAction
.Assert
, Flags
= SecurityPermissionFlag
.SerializationFormatter
)]
815 internal void SerializeWithAssert(Stream outputStream
, object stateGraph
) {
816 Serialize(outputStream
, stateGraph
);
820 /// Serializes an object graph into a binary serialized form within
821 /// the specified stream.
823 public void Serialize(Stream outputStream
, object stateGraph
) {
824 if (outputStream
== null) {
825 throw new ArgumentNullException("outputStream");
828 InitializeSerializer();
830 SerializerBinaryWriter writer
= new SerializerBinaryWriter(outputStream
);
831 writer
.Write(Marker_Format
);
832 writer
.Write(Marker_Version_1
);
833 SerializeValue(writer
, stateGraph
);
837 /// Serializes an IndexedString. If this is the first occurrence, it is written
838 /// out to the underlying stream, and is added to the string table for future
839 /// reference. Otherwise, a reference by index is written out.
841 private void SerializeIndexedString(SerializerBinaryWriter writer
, string s
) {
842 object id
= _stringTable
[s
];
844 writer
.Write(Token_IndexedString
);
845 writer
.Write((byte)(int)id
);
849 AddSerializationStringReference(s
);
851 writer
.Write(Token_IndexedStringAdd
);
856 /// Serializes a Type. If this is the first occurrence, the type name is written
857 /// out to the underlying stream, and the type is added to the string table for future
858 /// reference. Otherwise, a reference by index is written out.
860 private void SerializeType(SerializerBinaryWriter writer
, Type type
) {
861 object id
= _typeTable
[type
];
863 writer
.Write(Token_TypeRef
);
864 writer
.WriteEncoded((int)id
);
868 AddSerializationTypeReference(type
);
870 if (type
.Assembly
== HttpContext
.SystemWebAssembly
) {
871 writer
.Write(Token_TypeRefAddLocal
);
872 writer
.Write(type
.FullName
);
875 writer
.Write(Token_TypeRefAdd
);
876 writer
.Write(type
.AssemblyQualifiedName
);
881 /// Serializes a single value using the specified writer.
882 /// Handles exceptions to provide more information about the value being serialized.
884 private void SerializeValue(SerializerBinaryWriter writer
, object value) {
887 Stack objectStack
= new Stack();
888 objectStack
.Push(value);
891 value = objectStack
.Pop();
894 writer
.Write(Token_Null
);
898 // NOTE: These are ordered roughly in the order of frequency.
900 if (value is string) {
901 string s
= (string)value;
903 writer
.Write(Token_EmptyString
);
906 writer
.Write(Token_String
);
915 writer
.Write(Token_ZeroInt32
);
918 writer
.Write(Token_Int32
);
919 writer
.WriteEncoded(i
);
925 writer
.Write(Token_Pair
);
927 Pair p
= (Pair
)value;
928 objectStack
.Push(p
.Second
);
929 objectStack
.Push(p
.First
);
933 if (value is Triplet
) {
934 writer
.Write(Token_Triplet
);
936 Triplet t
= (Triplet
)value;
937 objectStack
.Push(t
.Third
);
938 objectStack
.Push(t
.Second
);
939 objectStack
.Push(t
.First
);
943 if (value is IndexedString
) {
944 Debug
.Assert(((IndexedString
)value).Value
!= null);
945 SerializeIndexedString(writer
, ((IndexedString
)value).Value
);
949 if (value.GetType() == typeof(ArrayList
)) {
950 writer
.Write(Token_ArrayList
);
952 ArrayList list
= (ArrayList
)value;
954 writer
.WriteEncoded(list
.Count
);
955 for (int i
= list
.Count
- 1; i
>= 0; i
--) {
956 objectStack
.Push(list
[i
]);
964 writer
.Write(Token_True
);
967 writer
.Write(Token_False
);
972 writer
.Write(Token_Byte
);
973 writer
.Write((byte)value);
977 writer
.Write(Token_Char
);
978 writer
.Write((char)value);
981 if (value is DateTime
) {
982 writer
.Write(Token_DateTime
);
983 writer
.Write(((DateTime
)value).ToBinary());
986 if (value is double) {
987 writer
.Write(Token_Double
);
988 writer
.Write((double)value);
991 if (value is short) {
992 writer
.Write(Token_Int16
);
993 writer
.Write((short)value);
996 if (value is float) {
997 writer
.Write(Token_Single
);
998 writer
.Write((float)value);
1002 if (value is IDictionary
) {
1003 bool canSerializeDictionary
= false;
1005 if (value.GetType() == typeof(Hashtable
)) {
1006 writer
.Write(Token_Hashtable
);
1007 canSerializeDictionary
= true;
1009 else if (value.GetType() == typeof(HybridDictionary
)) {
1010 writer
.Write(Token_HybridDictionary
);
1011 canSerializeDictionary
= true;
1014 if (canSerializeDictionary
) {
1015 IDictionary table
= (IDictionary
)value;
1017 writer
.WriteEncoded(table
.Count
);
1018 if (table
.Count
!= 0) {
1019 foreach (DictionaryEntry entry
in table
) {
1020 objectStack
.Push(entry
.Value
);
1021 objectStack
.Push(entry
.Key
);
1029 if (value is EventValidationStore
) {
1030 writer
.Write(Token_EventValidationStore
);
1031 ((EventValidationStore
)value).SerializeTo(writer
.BaseStream
);
1035 if (value is Type
) {
1036 writer
.Write(Token_Type
);
1037 SerializeType(writer
, (Type
)value);
1041 Type valueType
= value.GetType();
1043 if (value is Array
) {
1044 // We only support Arrays with rank 1 (No multi dimensional arrays
1045 if (((Array
)value).Rank
> 1) {
1049 Type underlyingType
= valueType
.GetElementType();
1051 if (underlyingType
== typeof(string)) {
1052 string[] strings
= (string[])value;
1053 bool containsNulls
= false;
1054 for (int i
= 0; i
< strings
.Length
; i
++) {
1055 if (strings
[i
] == null) {
1056 // Will have to treat these as generic arrays since we
1057 // can't represent nulls in the binary stream, without
1058 // writing out string token markers.
1059 // Generic array writing includes the token markers.
1060 containsNulls
= true;
1065 if (!containsNulls
) {
1066 writer
.Write(Token_StringArray
);
1067 writer
.WriteEncoded(strings
.Length
);
1068 for (int i
= 0; i
< strings
.Length
; i
++) {
1069 writer
.Write(strings
[i
]);
1075 Array values
= (Array
)value;
1077 // Optimize for sparse arrays, if the array is more than 3/4 nulls
1078 if (values
.Length
> 3) {
1079 int sparseThreshold
= (values
.Length
/ 4) + 1;
1081 List
<int> items
= new List
<int>(sparseThreshold
);
1082 for (int i
= 0; i
< values
.Length
; ++i
) {
1083 if (values
.GetValue(i
) != null) {
1085 if (numValues
>= sparseThreshold
) {
1092 // We have enough nulls to use sparse array format <index, value, index, value, ...>
1093 if (numValues
< sparseThreshold
) {
1094 writer
.Write(Token_SparseArray
);
1095 SerializeType(writer
, underlyingType
);
1097 writer
.WriteEncoded(values
.Length
);
1098 writer
.WriteEncoded(numValues
);
1100 // Now we need to just serialize pairs representing the index, and the item
1101 foreach (int index
in items
) {
1102 writer
.WriteEncoded(index
);
1103 SerializeValue(writer
, values
.GetValue(index
));
1110 writer
.Write(Token_Array
);
1111 SerializeType(writer
, underlyingType
);
1113 writer
.WriteEncoded(values
.Length
);
1114 for (int i
= values
.Length
- 1; i
>= 0; i
--) {
1115 objectStack
.Push(values
.GetValue(i
));
1121 if (valueType
.IsEnum
) {
1122 Type underlyingType
= Enum
.GetUnderlyingType(valueType
);
1123 if (underlyingType
== typeof(int)) {
1124 writer
.Write(Token_IntEnum
);
1125 SerializeType(writer
, valueType
);
1126 writer
.WriteEncoded((int)value);
1132 if (valueType
== typeof(Color
)) {
1133 Color c
= (Color
)value;
1135 writer
.Write(Token_EmptyColor
);
1138 if (!c
.IsNamedColor
) {
1139 writer
.Write(Token_Color
);
1140 writer
.Write(c
.ToArgb());
1144 writer
.Write(Token_KnownColor
);
1145 writer
.WriteEncoded((int)c
.ToKnownColor());
1150 if (value is Unit
) {
1151 Unit uval
= (Unit
)value;
1153 writer
.Write(Token_EmptyUnit
);
1156 writer
.Write(Token_Unit
);
1157 writer
.Write(uval
.Value
);
1158 writer
.Write((int)uval
.Type
);
1163 // Handle the remaining types
1164 // First try to get a type converter, and then resort to
1165 // binary serialization if all else fails
1167 TypeConverter converter
= TypeDescriptor
.GetConverter(valueType
);
1168 bool canConvert
= System
.Web
.UI
.Util
.CanConvertToFrom(converter
, typeof(string));
1171 writer
.Write(Token_StringFormatted
);
1172 SerializeType(writer
, valueType
);
1173 writer
.Write(converter
.ConvertToInvariantString(null, value));
1176 IFormatter formatter
= new BinaryFormatter();
1177 MemoryStream ms
= new MemoryStream(256);
1178 formatter
.Serialize(ms
, value);
1180 byte[] buffer
= ms
.GetBuffer();
1181 int length
= (int)ms
.Length
;
1183 writer
.Write(Token_BinarySerialized
);
1184 writer
.WriteEncoded(length
);
1185 if (buffer
.Length
!= 0) {
1186 writer
.Write(buffer
, 0, (int)length
);
1190 while (objectStack
.Count
> 0);
1192 catch (Exception serializationException
) {
1194 throw new ArgumentException(SR
.GetString(SR
.ErrorSerializingValue
, value.ToString(), value.GetType().FullName
),
1195 serializationException
);
1196 throw serializationException
;
1200 #region Implementation of IStateFormatter
1201 object IStateFormatter
.Deserialize(string serializedState
) {
1202 return Deserialize(serializedState
);
1205 string IStateFormatter
.Serialize(object state
) {
1206 return Serialize(state
);
1210 #region Implementation of IFormatter
1213 SerializationBinder IFormatter
.Binder
{
1223 StreamingContext IFormatter
.Context
{
1225 return new StreamingContext(StreamingContextStates
.All
);
1233 ISurrogateSelector IFormatter
.SurrogateSelector
{
1243 object IFormatter
.Deserialize(Stream serializationStream
) {
1244 return Deserialize(serializationStream
);
1249 void IFormatter
.Serialize(Stream serializationStream
, object stateGraph
) {
1250 Serialize(serializationStream
, stateGraph
);
1254 #region IStateFormatter2 Members
1255 object IStateFormatter2
.Deserialize(string serializedState
, Purpose purpose
) {
1256 return Deserialize(serializedState
, purpose
);
1259 string IStateFormatter2
.Serialize(object state
, Purpose purpose
) {
1260 return Serialize(state
, purpose
);
1265 /// Custom BinaryReader used during the deserialization.
1267 private sealed class SerializerBinaryReader
: BinaryReader
{
1269 public SerializerBinaryReader(Stream stream
) : base(stream
) {
1272 public int ReadEncodedInt32() {
1273 return Read7BitEncodedInt();
1279 /// Custom BinaryWriter used during the serialization.
1281 private sealed class SerializerBinaryWriter
: BinaryWriter
{
1283 public SerializerBinaryWriter(Stream stream
) : base(stream
) {
1286 public void WriteEncoded(int value) {
1289 uint v
= (uint)value;
1291 Write((byte)(v
| 0x80));