| File: BinarySerialization.cs | Web Access |
| Project: src\winforms\src\Common\tests\TestUtilities\System.Private.Windows.Core.TestUtilities.csproj (System.Private.Windows.Core.TestUtilities) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Formats.Nrbf; using System.Reflection; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters; using System.Runtime.Serialization.Formatters.Binary; #if NET using System.Runtime.CompilerServices; #endif namespace System; public static class BinarySerialization { /// <summary> /// Ensures the list of types marked as serializable under <paramref name="assemblyUnderTest"/> matches /// <paramref name="serializableTypes"/>. If not, <see cref="NotSupportedException"/> is thrown. /// </summary> public static void EnsureSerializableAttribute(Assembly assemblyUnderTest, HashSet<string> serializableTypes) { foreach (Type type in assemblyUnderTest.GetTypes()) { var attributes = Attribute.GetCustomAttributes(type); string? fullName = type.FullName; if (fullName is null) { continue; } if (!attributes.Any(a => a is SerializableAttribute)) { // The type isn't marked as serializable, verify it is not one of the types // that we expect to be serializable if (serializableTypes.Contains(fullName)) { throw new NotSupportedException($"Serializable attribute is expected on {fullName}"); } continue; } if (attributes.Any(a => a is CompilerGeneratedAttribute)) { // ignore compiler generated types, we have no control over them continue; } // Ensure SerializableAttribute is not added to any types not in the known list. if (serializableTypes.Contains(fullName)) { // we have marked the type as serializable, all good continue; } throw new NotSupportedException($"Serializable attribute is not expected on {type.FullName}"); } } /// <summary> /// Binary deserializes a base 64 string to <typeparamref name="T"/>. <paramref name="blob"/> is binary /// deserialized with <see cref="BinaryFormatter"/> with <paramref name="assemblyStyle"/> taken into account. /// </summary> #pragma warning disable SYSLIB0050 // Type or member is obsolete public static T EnsureDeserialize<T>(string blob, FormatterAssemblyStyle assemblyStyle = FormatterAssemblyStyle.Simple) { object @object = FromBase64String(blob, assemblyStyle); Assert.NotNull(@object); return Assert.IsType<T>(@object); static object FromBase64String(string base64String, FormatterAssemblyStyle assemblyStyle) { byte[] raw = Convert.FromBase64String(base64String); return FromByteArray(raw, assemblyStyle); } static object FromByteArray(byte[] raw, FormatterAssemblyStyle assemblyStyle) { using MemoryStream serializedStream = new(raw); return DeserializeFromStream(serializedStream); } } /// <summary> /// Returns a base 64 string of the binary serialized <paramref name="object"/>. /// <paramref name="object"/> is binary serialized using <see cref="BinaryFormatter"/> /// with <paramref name="assemblyFormat"/> taken into account. /// </summary> public static string ToBase64String( object @object, FormatterAssemblyStyle assemblyFormat = FormatterAssemblyStyle.Simple) { byte[] raw = ToByteArray(@object, assemblyFormat); return Convert.ToBase64String(raw); static byte[] ToByteArray(object @object, FormatterAssemblyStyle assemblyFormat) { using MemoryStream stream = new(); stream.WriteBinaryFormat(@object, assemblyFormat: assemblyFormat); return stream.ToArray(); } } /// <summary> /// Serializes the specified object using <see cref="BinaryFormatter"/>. /// </summary> /// <param name="object">The object to clone.</param> /// <param name="stream">The stream to write to.</param> /// <param name="assemblyFormat">The assembly format to use.</param> /// <param name="typeFormat">The type format to use.</param> /// <exception cref="ArgumentNullException">Thrown when the specified object is <see langword="null"/>.</exception> public static void WriteBinaryFormat( this Stream stream, object @object, FormatterAssemblyStyle assemblyFormat = FormatterAssemblyStyle.Simple, FormatterTypeStyle typeFormat = FormatterTypeStyle.TypesAlways) { ArgumentNullException.ThrowIfNull(@object); #pragma warning disable SYSLIB0011 // Type or member is obsolete BinaryFormatter formatter = new() // CodeQL [SM04191]: Safe use because the deserialization process is performed on trusted data and the types are controlled and validated. { #pragma warning restore SYSLIB0011 AssemblyFormat = assemblyFormat, TypeFormat = typeFormat }; using BinaryFormatterScope scope = new(enable: true); formatter.Serialize(stream, @object); } /// <summary> /// Deserializes the specified stream using <see cref="BinaryFormatter"/>. /// </summary> /// <param name="stream">The stream to read from.</param> /// <param name="assemblyFormat">The assembly format to use.</param> /// <param name="binder">The serialization binder to use, if any.</param> /// <returns>The deserialized object.</returns> /// <exception cref="ArgumentNullException">Thrown when the specified stream is <see langword="null"/>.</exception> /// <exception cref="SerializationException">Thrown when an error occurs during deserialization.</exception> public static object DeserializeFromStream( Stream stream, FormatterAssemblyStyle assemblyFormat = FormatterAssemblyStyle.Simple, SerializationBinder? binder = null) { #pragma warning disable SYSLIB0011 // Type or member is obsolete BinaryFormatter formatter = new() // CodeQL [SM04191]: Safe use because the deserialization process is performed on trusted data and the types are controlled and validated. { #pragma warning restore SYSLIB0011 // Type or member is obsolete AssemblyFormat = assemblyFormat, Binder = binder }; using BinaryFormatterScope scope = new(enable: true); return formatter.Deserialize(stream); // CodeQL[SM03722] : Testing legacy feature. This is a safe use of BinaryFormatter because the data is trusted and the types are controlled and validated. } #pragma warning restore SYSLIB0050 #pragma warning disable SYSLIB5005 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. /// <summary> /// Formats the given object to a binary stream and decodes it using the NRBF deserializer. /// </summary> /// <param name="object">The object to binary format.</param> /// <inheritdoc cref="NrbfDecoder.Decode(Stream, out IReadOnlyDictionary{SerializationRecordId, SerializationRecord}, PayloadOptions?, bool)"/> public static SerializationRecord SerializeAndDecode( this object @object, out IReadOnlyDictionary<SerializationRecordId, SerializationRecord> recordMap) { using MemoryStream stream = new(); stream.WriteBinaryFormat(@object); stream.Position = 0; return NrbfDecoder.Decode(stream, out recordMap, options: null, leaveOpen: true); } public static SerializationRecord SerializeAndDecode(this object @object) => SerializeAndDecode(@object, out _); #pragma warning restore SYSLIB5005 }