Updates referencesource to .NET 4.7
[mono-project.git] / mcs / class / referencesource / System.Web / UI / ObjectStateFormatter.cs
blob7e60b164e4acbd7bdaa4b4c6366946821ddd2bd8
1 //------------------------------------------------------------------------------
2 // <copyright file="ObjectStateFormatter.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
7 namespace System.Web.UI {
9 using System;
10 using System.Collections;
11 using System.Collections.Generic;
12 using System.Collections.Specialized;
13 using System.ComponentModel;
14 using System.Drawing;
15 using System.IO;
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;
22 using System.Text;
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;
30 //
34 /// <devdoc>
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.
44 /// </devdoc>
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;
95 // Constant values
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 =
108 new Type[] {
109 typeof(object),
110 typeof(int),
111 typeof(string),
112 typeof(bool)
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
148 private Page _page;
150 /// <devdoc>
151 /// Initializes a new instance of the ObjectStateFormatter.
152 /// </devdoc>
153 public ObjectStateFormatter() : this(null) {
156 /// <internalonly/>
157 /// <devdoc>
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
160 /// purposes.
161 /// NOTE: this constructor is mainly for LOSFormatter's consumption, not used internally
162 /// </devdoc>
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;
171 /// <internalonly/>
172 /// <devdoc>
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.
177 /// </devdoc>
179 internal ObjectStateFormatter(Page page, bool throwOnErrorDeserializing) {
180 _page = page;
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
188 if (_page == null) {
189 return null;
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
215 if (_page == null) {
216 return null;
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);
232 else {
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);
241 return _macKeyBytes;
244 /// <devdoc>
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.
249 /// </devdoc>
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;
259 _stringTableCount++;
262 /// <devdoc>
263 /// Adds a type reference during the deserialization process,
264 /// so that it can be referred to later by its index.
265 /// </devdoc>
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.
270 _typeList.Add(type);
273 /// <devdoc>
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.
278 /// </devdoc>
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;
297 _stringTableCount++;
300 /// <devdoc>
301 /// Adds a type reference during the serialization process, so it
302 /// can be later referred to by its index.
303 /// </devdoc>
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);
316 /// <devdoc>
317 /// Deserializes an object graph from its binary serialized form
318 /// contained in the specified stream.
319 /// </devdoc>
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);
330 try {
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);
354 /// <devdoc>
355 /// Deserializes an object graph from its textual serialized form
356 /// contained in the specified string.
357 /// </devdoc>
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
374 try {
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;
386 else {
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
400 catch {
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();
408 try {
409 objectStream.Write(inputBytes, 0, length);
410 objectStream.Position = 0;
411 result = Deserialize(objectStream);
413 finally {
414 ReleaseMemoryStream(objectStream);
416 return result;
419 /// <devdoc>
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.
422 /// </devdoc>
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]);
433 else {
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);
443 /// <devdoc>
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.
448 /// </devdoc>
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];
460 else {
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;
466 try {
467 if (token == Token_TypeRefAddLocal) {
468 resolvedType = HttpContext.SystemWebAssembly.GetType(typeName, true);
470 else {
471 resolvedType = Type.GetType(typeName, true);
474 catch (Exception exception) {
475 if (_throwOnErrorDeserializing) {
476 throw;
478 else {
479 // Log error message
480 WebBaseEvent.RaiseSystemEvent(
481 SR.GetString(SR.Webevent_msg_OSF_Deserialization_Type, typeName),
482 this,
483 WebEventCodes.WebErrorObjectStateFormatterDeserializationError,
484 WebEventCodes.UndefinedEventDetailCode,
485 exception);
489 AddDeserializationTypeReference(resolvedType);
490 return resolvedType;
494 /// <devdoc>
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.
498 /// </devdoc>
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.
505 switch (token) {
506 case Token_Null:
507 return null;
508 case Token_EmptyString:
509 return String.Empty;
510 case Token_String:
511 return reader.ReadString();
512 case Token_ZeroInt32:
513 return 0;
514 case Token_Int32:
515 return reader.ReadEncodedInt32();
516 case Token_Pair:
517 return new Pair(DeserializeValue(reader),
518 DeserializeValue(reader));
519 case Token_Triplet:
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));
534 return list;
536 case Token_True:
537 return true;
538 case Token_False:
539 return false;
540 case Token_Byte:
541 return reader.ReadByte();
542 case Token_Char:
543 return reader.ReadChar();
544 case Token_DateTime:
545 return DateTime.FromBinary(reader.ReadInt64());
546 case Token_Double:
547 return reader.ReadDouble();
548 case Token_Int16:
549 return reader.ReadInt16();
550 case Token_Single:
551 return reader.ReadSingle();
552 case Token_Hashtable:
553 case Token_HybridDictionary:
555 int count = reader.ReadEncodedInt32();
557 IDictionary table;
558 if (token == Token_Hashtable) {
559 table = new Hashtable(count);
561 else {
562 table = new HybridDictionary(count);
564 for (int i = 0; i < count; i++) {
565 table.Add(DeserializeValue(reader),
566 DeserializeValue(reader));
569 return table;
571 case Token_Type:
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();
582 return array;
584 case Token_Array:
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);
594 return list;
596 case Token_IntEnum:
598 Type enumType = DeserializeType(reader);
599 int enumValue = reader.ReadEncodedInt32();
601 return Enum.ToObject(enumType, enumValue);
603 case Token_Color:
604 return Color.FromArgb(reader.ReadInt32());
605 case Token_EmptyColor:
606 return Color.Empty;
607 case Token_KnownColor:
608 return Color.FromKnownColor((KnownColor)reader.ReadEncodedInt32());
609 case Token_Unit:
610 return new Unit(reader.ReadDouble(), (UnitType)reader.ReadInt32());
611 case Token_EmptyUnit:
612 return Unit.Empty;
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);
638 return list;
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.
651 try {
652 result = converter.ConvertFromInvariantString(formattedValue);
654 catch (Exception exception) {
655 if (_throwOnErrorDeserializing) {
656 throw;
658 else {
659 WebBaseEvent.RaiseSystemEvent(
660 SR.GetString(SR.Webevent_msg_OSF_Deserialization_String, valueType.AssemblyQualifiedName),
661 this,
662 WebEventCodes.WebErrorObjectStateFormatterDeserializationError,
663 WebEventCodes.UndefinedEventDetailCode,
664 exception);
669 return result;
671 case Token_BinarySerialized:
673 int length = reader.ReadEncodedInt32();
675 byte[] buffer = new byte[length];
676 if (length != 0) {
677 reader.Read(buffer, 0, length);
680 object result = null;
681 MemoryStream ms = GetMemoryStream();
682 try {
683 ms.Write(buffer, 0, length);
684 ms.Position = 0;
685 IFormatter formatter = new BinaryFormatter();
687 result = formatter.Deserialize(ms);
689 catch (Exception exception) {
690 if (_throwOnErrorDeserializing) {
691 throw;
693 else {
694 WebBaseEvent.RaiseSystemEvent(
695 SR.GetString(SR.Webevent_msg_OSF_Deserialization_Binary),
696 this,
697 WebEventCodes.WebErrorObjectStateFormatterDeserializationError,
698 WebEventCodes.UndefinedEventDetailCode,
699 exception);
702 finally {
703 ReleaseMemoryStream(ms);
705 return result;
707 default:
708 throw new InvalidOperationException(SR.GetString(SR.InvalidSerializedData));
712 /// <devdoc>
713 /// Retrieves a MemoryStream instance.
714 /// </devdoc>
715 private static MemoryStream GetMemoryStream() {
716 return new MemoryStream(2048);
720 /// <devdoc>
721 /// Initializes this instance to perform deserialization.
722 /// </devdoc>
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;
734 /// <devdoc>
735 /// Initializes this instance to perform serialization.
736 /// </devdoc>
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;
749 /// <devdoc>
750 /// Releases a MemoryStream instance.
751 /// </devdoc>
752 private static void ReleaseMemoryStream(MemoryStream stream) {
753 stream.Dispose();
756 /// <devdoc>
757 /// Serializes an object graph into a textual serialized form.
758 /// </devdoc>
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();
770 try {
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;
791 else {
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);
808 finally {
809 ReleaseMemoryStream(ms);
811 return result;
814 [SecurityPermission(SecurityAction.Assert, Flags = SecurityPermissionFlag.SerializationFormatter)]
815 internal void SerializeWithAssert(Stream outputStream, object stateGraph) {
816 Serialize(outputStream, stateGraph);
819 /// <devdoc>
820 /// Serializes an object graph into a binary serialized form within
821 /// the specified stream.
822 /// </devdoc>
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);
836 /// <devdoc>
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.
840 /// </devdoc>
841 private void SerializeIndexedString(SerializerBinaryWriter writer, string s) {
842 object id = _stringTable[s];
843 if (id != null) {
844 writer.Write(Token_IndexedString);
845 writer.Write((byte)(int)id);
846 return;
849 AddSerializationStringReference(s);
851 writer.Write(Token_IndexedStringAdd);
852 writer.Write(s);
855 /// <devdoc>
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.
859 /// </devdoc>
860 private void SerializeType(SerializerBinaryWriter writer, Type type) {
861 object id = _typeTable[type];
862 if (id != null) {
863 writer.Write(Token_TypeRef);
864 writer.WriteEncoded((int)id);
865 return;
868 AddSerializationTypeReference(type);
870 if (type.Assembly == HttpContext.SystemWebAssembly) {
871 writer.Write(Token_TypeRefAddLocal);
872 writer.Write(type.FullName);
874 else {
875 writer.Write(Token_TypeRefAdd);
876 writer.Write(type.AssemblyQualifiedName);
880 /// <devdoc>
881 /// Serializes a single value using the specified writer.
882 /// Handles exceptions to provide more information about the value being serialized.
883 /// </devdoc>
884 private void SerializeValue(SerializerBinaryWriter writer, object value) {
885 try {
887 Stack objectStack = new Stack();
888 objectStack.Push(value);
890 do {
891 value = objectStack.Pop();
893 if (value == null) {
894 writer.Write(Token_Null);
895 continue;
898 // NOTE: These are ordered roughly in the order of frequency.
900 if (value is string) {
901 string s = (string)value;
902 if (s.Length == 0) {
903 writer.Write(Token_EmptyString);
905 else {
906 writer.Write(Token_String);
907 writer.Write(s);
909 continue;
912 if (value is int) {
913 int i = (int)value;
914 if (i == 0) {
915 writer.Write(Token_ZeroInt32);
917 else {
918 writer.Write(Token_Int32);
919 writer.WriteEncoded(i);
921 continue;
924 if (value is Pair) {
925 writer.Write(Token_Pair);
927 Pair p = (Pair)value;
928 objectStack.Push(p.Second);
929 objectStack.Push(p.First);
930 continue;
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);
940 continue;
943 if (value is IndexedString) {
944 Debug.Assert(((IndexedString)value).Value != null);
945 SerializeIndexedString(writer, ((IndexedString)value).Value);
946 continue;
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]);
959 continue;
962 if (value is bool) {
963 if (((bool)value)) {
964 writer.Write(Token_True);
966 else {
967 writer.Write(Token_False);
969 continue;
971 if (value is byte) {
972 writer.Write(Token_Byte);
973 writer.Write((byte)value);
974 continue;
976 if (value is char) {
977 writer.Write(Token_Char);
978 writer.Write((char)value);
979 continue;
981 if (value is DateTime) {
982 writer.Write(Token_DateTime);
983 writer.Write(((DateTime)value).ToBinary());
984 continue;
986 if (value is double) {
987 writer.Write(Token_Double);
988 writer.Write((double)value);
989 continue;
991 if (value is short) {
992 writer.Write(Token_Int16);
993 writer.Write((short)value);
994 continue;
996 if (value is float) {
997 writer.Write(Token_Single);
998 writer.Write((float)value);
999 continue;
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);
1025 continue;
1029 if (value is EventValidationStore) {
1030 writer.Write(Token_EventValidationStore);
1031 ((EventValidationStore)value).SerializeTo(writer.BaseStream);
1032 continue;
1035 if (value is Type) {
1036 writer.Write(Token_Type);
1037 SerializeType(writer, (Type)value);
1038 continue;
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) {
1046 continue;
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;
1061 break;
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]);
1071 continue;
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;
1080 int numValues = 0;
1081 List<int> items = new List<int>(sparseThreshold);
1082 for (int i = 0; i < values.Length; ++i) {
1083 if (values.GetValue(i) != null) {
1084 ++numValues;
1085 if (numValues >= sparseThreshold) {
1086 break;
1088 items.Add(i);
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));
1106 continue;
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));
1118 continue;
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);
1128 continue;
1132 if (valueType == typeof(Color)) {
1133 Color c = (Color)value;
1134 if (c.IsEmpty) {
1135 writer.Write(Token_EmptyColor);
1136 continue;
1138 if (!c.IsNamedColor) {
1139 writer.Write(Token_Color);
1140 writer.Write(c.ToArgb());
1141 continue;
1143 else {
1144 writer.Write(Token_KnownColor);
1145 writer.WriteEncoded((int)c.ToKnownColor());
1146 continue;
1150 if (value is Unit) {
1151 Unit uval = (Unit)value;
1152 if (uval.IsEmpty) {
1153 writer.Write(Token_EmptyUnit);
1155 else {
1156 writer.Write(Token_Unit);
1157 writer.Write(uval.Value);
1158 writer.Write((int)uval.Type);
1160 continue;
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));
1170 if (canConvert) {
1171 writer.Write(Token_StringFormatted);
1172 SerializeType(writer, valueType);
1173 writer.Write(converter.ConvertToInvariantString(null, value));
1175 else {
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) {
1193 if (value != null)
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);
1208 #endregion
1210 #region Implementation of IFormatter
1212 /// <internalonly/>
1213 SerializationBinder IFormatter.Binder {
1214 get {
1215 return null;
1217 set {
1222 /// <internalonly/>
1223 StreamingContext IFormatter.Context {
1224 get {
1225 return new StreamingContext(StreamingContextStates.All);
1227 set {
1232 /// <internalonly/>
1233 ISurrogateSelector IFormatter.SurrogateSelector {
1234 get {
1235 return null;
1237 set {
1242 /// <internalonly/>
1243 object IFormatter.Deserialize(Stream serializationStream) {
1244 return Deserialize(serializationStream);
1248 /// <internalonly/>
1249 void IFormatter.Serialize(Stream serializationStream, object stateGraph) {
1250 Serialize(serializationStream, stateGraph);
1252 #endregion
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);
1262 #endregion
1264 /// <devdoc>
1265 /// Custom BinaryReader used during the deserialization.
1266 /// </devdoc>
1267 private sealed class SerializerBinaryReader : BinaryReader {
1269 public SerializerBinaryReader(Stream stream) : base(stream) {
1272 public int ReadEncodedInt32() {
1273 return Read7BitEncodedInt();
1278 /// <devdoc>
1279 /// Custom BinaryWriter used during the serialization.
1280 /// </devdoc>
1281 private sealed class SerializerBinaryWriter : BinaryWriter {
1283 public SerializerBinaryWriter(Stream stream) : base(stream) {
1286 public void WriteEncoded(int value) {
1289 uint v = (uint)value;
1290 while (v >= 0x80) {
1291 Write((byte)(v | 0x80));
1292 v >>= 7;
1294 Write((byte)v);