File: System\Resources\Extensions\DeserializingResourceReader.cs
Web Access
Project: src\src\libraries\System.Resources.Extensions\src\System.Resources.Extensions.csproj (System.Resources.Extensions)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.ComponentModel;
using System.IO;
using System.Resources.Extensions.BinaryFormat;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
 
namespace System.Resources.Extensions
{
    public partial class DeserializingResourceReader
    {
        private static readonly bool s_useBinaryFormatter = AppContext.TryGetSwitch("System.Resources.Extensions.UseBinaryFormatter", out bool isEnabled) && isEnabled;
 
        private bool _assumeBinaryFormatter;
 
        private bool ValidateReaderType(string readerType)
        {
            // our format?
            if (TypeNameComparer.Instance.Equals(readerType, PreserializedResourceWriter.DeserializingResourceReaderFullyQualifiedName))
            {
                return true;
            }
 
            // default format?
            if (TypeNameComparer.Instance.Equals(readerType, PreserializedResourceWriter.ResourceReaderFullyQualifiedName))
            {
                // we can read the default format, we just assume BinaryFormatter and don't
                // read the SerializationFormat
                _assumeBinaryFormatter = true;
                return true;
            }
 
            return false;
        }
 
        private object ReadBinaryFormattedObject()
        {
            if (!s_useBinaryFormatter)
            {
                BinaryFormattedObject binaryFormattedObject = new(_store.BaseStream);
 
                return binaryFormattedObject.Deserialize();
            }
            else
            {
#pragma warning disable SYSLIB0011
                BinaryFormatter? formatter = new()
                {
                    Binder = new UndoTruncatedTypeNameSerializationBinder()
                };
                return formatter.Deserialize(_store.BaseStream);
#pragma warning restore SYSLIB0011
            }
        }
 
        internal sealed class UndoTruncatedTypeNameSerializationBinder : SerializationBinder
        {
            public override Type? BindToType(string assemblyName, string typeName)
            {
                Type? type = null;
 
                // determine if we have a mangled generic type name
                if (typeName != null && assemblyName != null && !AreBracketsBalanced(typeName))
                {
                    // undo the mangling that may have happened with .NETFramework's
                    // incorrect ResXSerialization binder.
                    typeName = typeName + ", " + assemblyName;
 
                    type = Type.GetType(typeName, throwOnError: false, ignoreCase: false);
                }
 
                // if type is null we'll fall back to the default type binder which is preferable
                // since it is backed by a cache
                return type;
            }
 
            private static bool AreBracketsBalanced(string typeName)
            {
                // make sure brackets are balanced
                int firstBracket = typeName.IndexOf('[');
 
                if (firstBracket == -1)
                {
                    return true;
                }
 
                int brackets = 1;
                for (int i = firstBracket + 1; i < typeName.Length; i++)
                {
                    if (typeName[i] == '[')
                    {
                        brackets++;
                    }
                    else if (typeName[i] == ']')
                    {
                        brackets--;
 
                        if (brackets < 0)
                        {
                            // unbalanced, closing bracket without opening
                            break;
                        }
                    }
                }
 
                return brackets == 0;
            }
 
        }
 
        private object DeserializeObject(int typeIndex)
        {
            Type type = FindType(typeIndex);
 
            if (_assumeBinaryFormatter)
            {
                return ReadBinaryFormattedObject();
            }
 
            // read type
            SerializationFormat format = (SerializationFormat)_store.Read7BitEncodedInt();
 
            object value;
 
            // read data
            switch (format)
            {
                case SerializationFormat.BinaryFormatter:
                    {
                        // read length
                        int length = _store.Read7BitEncodedInt();
                        if (length < 0)
                        {
                            throw new BadImageFormatException(SR.Format(SR.BadImageFormat_ResourceDataLengthInvalid, length));
                        }
 
                        long originalPosition = _store.BaseStream.Position;
 
                        value = ReadBinaryFormattedObject();
 
                        if (type == typeof(UnknownType))
                        {
                            // type information was omitted at the time of writing
                            // allow the payload to define the type
                            type = value.GetType();
                        }
 
                        long bytesRead = _store.BaseStream.Position - originalPosition;
 
                        // Ensure BF read what we expected.
                        if (bytesRead != length)
                        {
                            throw new BadImageFormatException(SR.Format(SR.BadImageFormat_ResourceDataLengthInvalid, length));
                        }
                        break;
                    }
                case SerializationFormat.TypeConverterByteArray:
                    {
                        // read length
                        int length = _store.Read7BitEncodedInt();
                        if (length < 0)
                        {
                            throw new BadImageFormatException(SR.Format(SR.BadImageFormat_ResourceDataLengthInvalid, length));
                        }
 
                        byte[] data = _store.ReadBytes(length);
 
                        TypeConverter converter = TypeDescriptor.GetConverter(type);
 
                        if (converter == null)
                        {
                            throw new TypeLoadException(SR.Format(SR.TypeLoadException_CannotLoadConverter, type));
                        }
 
                        value = converter.ConvertFrom(data)!;
                        break;
                    }
                case SerializationFormat.TypeConverterString:
                    {
                        string stringData = _store.ReadString();
 
                        TypeConverter converter = TypeDescriptor.GetConverter(type);
 
                        if (converter == null)
                        {
                            throw new TypeLoadException(SR.Format(SR.TypeLoadException_CannotLoadConverter, type));
                        }
 
                        value = converter.ConvertFromInvariantString(stringData)!;
                        break;
                    }
                case SerializationFormat.ActivatorStream:
                    {
                        // read length
                        int length = _store.Read7BitEncodedInt();
                        if (length < 0)
                        {
                            throw new BadImageFormatException(SR.Format(SR.BadImageFormat_ResourceDataLengthInvalid, length));
                        }
                        Stream stream;
 
                        if (_store.BaseStream is UnmanagedMemoryStream ums)
                        {
                            // For the case that we've memory mapped in the .resources
                            // file, just return a Stream pointing to that block of memory.
                            unsafe
                            {
                                stream = new UnmanagedMemoryStream(ums.PositionPointer, length, length, FileAccess.Read);
                            }
                        }
                        else
                        {
 
                            byte[] bytes = _store.ReadBytes(length);
                            // Lifetime of memory == lifetime of this stream.
                            stream = new MemoryStream(bytes, false);
                        }
 
                        value = Activator.CreateInstance(type, new object[] { stream })!;
                        break;
                    }
                default:
                    throw new BadImageFormatException(SR.BadImageFormat_TypeMismatch);
            }
 
            // Make sure we deserialized the type that we expected.
            // This protects against bad typeconverters or bad binaryformatter payloads.
            if (value.GetType() != type)
                throw new BadImageFormatException(SR.Format(SR.BadImageFormat_ResType_SerBlobMismatch, type.FullName, value.GetType().FullName));
 
            return value;
        }
    }
}