diff options
Diffstat (limited to 'src/mscorlib/src/System/Resources/ResourceReader.cs')
-rw-r--r-- | src/mscorlib/src/System/Resources/ResourceReader.cs | 1424 |
1 files changed, 1424 insertions, 0 deletions
diff --git a/src/mscorlib/src/System/Resources/ResourceReader.cs b/src/mscorlib/src/System/Resources/ResourceReader.cs new file mode 100644 index 0000000000..a269d5c5fe --- /dev/null +++ b/src/mscorlib/src/System/Resources/ResourceReader.cs @@ -0,0 +1,1424 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** +** +** +** +** Purpose: Default way to read streams of resources on +** demand. +** +** Version 2 support on October 6, 2003 +** +===========================================================*/ +namespace System.Resources { + using System; + using System.IO; + using System.Text; + using System.Collections; + using System.Collections.Generic; +#if FEATURE_SERIALIZATION + using System.Runtime.Serialization; + using System.Runtime.Serialization.Formatters; + using System.Runtime.Serialization.Formatters.Binary; +#endif // FEATURE_SERIALIZATION + using System.Reflection; + using System.Security.Permissions; + using System.Security; + using System.Globalization; + using System.Configuration.Assemblies; + using System.Runtime.Versioning; + using System.Diagnostics.Contracts; + + // Provides the default implementation of IResourceReader, reading + // .resources file from the system default binary format. This class + // can be treated as an enumerator once. + // + // See the RuntimeResourceSet overview for details on the system + // default file format. + // + + internal struct ResourceLocator + { + internal Object _value; // Can be null. Consider WeakReference instead? + internal int _dataPos; + + internal ResourceLocator(int dataPos, Object value) + { + _dataPos = dataPos; + _value = value; + } + + internal int DataPosition { + get { return _dataPos; } + //set { _dataPos = value; } + } + + // Allows adding in profiling data in a future version, or a special + // resource profiling build. We could also use WeakReference. + internal Object Value { + get { return _value; } + set { _value = value; } + } + + internal static bool CanCache(ResourceTypeCode value) + { + Contract.Assert(value >= 0, "negative ResourceTypeCode. What?"); + return value <= ResourceTypeCode.LastPrimitive; + } + } + + + [System.Runtime.InteropServices.ComVisible(true)] + public sealed class ResourceReader : IResourceReader + { + // A reasonable default buffer size for reading from files, especially + // when we will likely be seeking frequently. Could be smaller, but does + // it make sense to use anything less than one page? + private const int DefaultFileStreamBufferSize = 4096; + + private BinaryReader _store; // backing store we're reading from. + // Used by RuntimeResourceSet and this class's enumerator. Maps + // resource name to a value, a ResourceLocator, or a + // LooselyLinkedManifestResource. + internal Dictionary<String, ResourceLocator> _resCache; + private long _nameSectionOffset; // Offset to name section of file. + private long _dataSectionOffset; // Offset to Data section of file. + + // Note this class is tightly coupled with UnmanagedMemoryStream. + // At runtime when getting an embedded resource from an assembly, + // we're given an UnmanagedMemoryStream referring to the mmap'ed portion + // of the assembly. The pointers here are pointers into that block of + // memory controlled by the OS's loader. + private int[] _nameHashes; // hash values for all names. + [SecurityCritical] + private unsafe int* _nameHashesPtr; // In case we're using UnmanagedMemoryStream + private int[] _namePositions; // relative locations of names + [SecurityCritical] + private unsafe int* _namePositionsPtr; // If we're using UnmanagedMemoryStream + private RuntimeType[] _typeTable; // Lazy array of Types for resource values. + private int[] _typeNamePositions; // To delay initialize type table +#if FEATURE_SERIALIZATION + private BinaryFormatter _objFormatter; // Deserialization stuff. +#endif // FEATURE_SERIALIZATION + private int _numResources; // Num of resources files, in case arrays aren't allocated. + + // We'll include a separate code path that uses UnmanagedMemoryStream to + // avoid allocating String objects and the like. + private UnmanagedMemoryStream _ums; + + // Version number of .resources file, for compatibility + private int _version; + +#if RESOURCE_FILE_FORMAT_DEBUG + private bool _debug; // Whether this file has debugging stuff in it. +#endif + +#if FEATURE_SERIALIZATION + private bool[] _safeToDeserialize; // Whether to assert serialization permission + private TypeLimitingDeserializationBinder _typeLimitingBinder; + + // One of our goals is to make sure localizable Windows Forms apps + // work in semi-trusted scenarios (ie, without serialization permission). + // Unfortunate we're serializing out some complex types that currently + // require a security check on deserialization. We may fix this + // in a next version, but for now just hard-code a list. + // Hard-code in the assembly name (minus version) so people can't spoof us. + private static readonly String[] TypesSafeForDeserialization = { + "System.String[], mscorlib, Culture=neutral, PublicKeyToken=" + AssemblyRef.MicrosoftPublicKeyToken, + "System.DateTime[], mscorlib, Culture=neutral, PublicKeyToken=" + AssemblyRef.MicrosoftPublicKeyToken, + "System.Drawing.Bitmap, System.Drawing, Culture=neutral, PublicKeyToken=" + AssemblyRef.MicrosoftPublicKeyToken, + "System.Drawing.Imaging.Metafile, System.Drawing, Culture=neutral, PublicKeyToken=" + AssemblyRef.MicrosoftPublicKeyToken, + "System.Drawing.Point, System.Drawing, Culture=neutral, PublicKeyToken=" + AssemblyRef.MicrosoftPublicKeyToken, + "System.Drawing.PointF, System.Drawing, Culture=neutral, PublicKeyToken=" + AssemblyRef.MicrosoftPublicKeyToken, + "System.Drawing.Size, System.Drawing, Culture=neutral, PublicKeyToken=" + AssemblyRef.MicrosoftPublicKeyToken, + "System.Drawing.SizeF, System.Drawing, Culture=neutral, PublicKeyToken=" + AssemblyRef.MicrosoftPublicKeyToken, + "System.Drawing.Font, System.Drawing, Culture=neutral, PublicKeyToken=" + AssemblyRef.MicrosoftPublicKeyToken, + "System.Drawing.Icon, System.Drawing, Culture=neutral, PublicKeyToken=" + AssemblyRef.MicrosoftPublicKeyToken, + "System.Drawing.Color, System.Drawing, Culture=neutral, PublicKeyToken=" + AssemblyRef.MicrosoftPublicKeyToken, + "System.Windows.Forms.Cursor, System.Windows.Forms, Culture=neutral, PublicKeyToken=" + AssemblyRef.EcmaPublicKeyToken, + "System.Windows.Forms.Padding, System.Windows.Forms, Culture=neutral, PublicKeyToken=" + AssemblyRef.EcmaPublicKeyToken, + "System.Windows.Forms.LinkArea, System.Windows.Forms, Culture=neutral, PublicKeyToken=" + AssemblyRef.EcmaPublicKeyToken, + "System.Windows.Forms.ImageListStreamer, System.Windows.Forms, Culture=neutral, PublicKeyToken=" + AssemblyRef.EcmaPublicKeyToken, + "System.Windows.Forms.ListViewGroup, System.Windows.Forms, Culture=neutral, PublicKeyToken=" + AssemblyRef.EcmaPublicKeyToken, + "System.Windows.Forms.ListViewItem, System.Windows.Forms, Culture=neutral, PublicKeyToken=" + AssemblyRef.EcmaPublicKeyToken, + "System.Windows.Forms.ListViewItem+ListViewSubItem, System.Windows.Forms, Culture=neutral, PublicKeyToken=" + AssemblyRef.EcmaPublicKeyToken, + "System.Windows.Forms.ListViewItem+ListViewSubItem+SubItemStyle, System.Windows.Forms, Culture=neutral, PublicKeyToken=" + AssemblyRef.EcmaPublicKeyToken, + "System.Windows.Forms.OwnerDrawPropertyBag, System.Windows.Forms, Culture=neutral, PublicKeyToken=" + AssemblyRef.EcmaPublicKeyToken, + "System.Windows.Forms.TreeNode, System.Windows.Forms, Culture=neutral, PublicKeyToken=" + AssemblyRef.EcmaPublicKeyToken + }; +#endif // FEATURE_SERIALIZATION + + #if FEATURE_CORECLR + [System.Security.SecurityCritical] // auto-generated + #else + [System.Security.SecuritySafeCritical] + #endif + public ResourceReader(String fileName) + { + _resCache = new Dictionary<String, ResourceLocator>(FastResourceComparer.Default); + _store = new BinaryReader(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read, DefaultFileStreamBufferSize, FileOptions.RandomAccess, Path.GetFileName(fileName), false), Encoding.UTF8); + BCLDebug.Log("RESMGRFILEFORMAT", "ResourceReader .ctor(String). UnmanagedMemoryStream: "+(_ums!=null)); + + try { + ReadResources(); + } + catch { + _store.Close(); // If we threw an exception, close the file. + throw; + } + } + + [System.Security.SecurityCritical] // auto-generated_required + public ResourceReader(Stream stream) + { + if (stream==null) + throw new ArgumentNullException("stream"); + if (!stream.CanRead) + throw new ArgumentException(Environment.GetResourceString("Argument_StreamNotReadable")); + Contract.EndContractBlock(); + + _resCache = new Dictionary<String, ResourceLocator>(FastResourceComparer.Default); + _store = new BinaryReader(stream, Encoding.UTF8); + // We have a faster code path for reading resource files from an assembly. + _ums = stream as UnmanagedMemoryStream; + + BCLDebug.Log("RESMGRFILEFORMAT", "ResourceReader .ctor(Stream). UnmanagedMemoryStream: "+(_ums!=null)); + ReadResources(); + } + + // This is the constructor the RuntimeResourceSet calls, + // passing in the stream to read from and the RuntimeResourceSet's + // internal hash table (hash table of names with file offsets + // and values, coupled to this ResourceReader). + [System.Security.SecurityCritical] // auto-generated + internal ResourceReader(Stream stream, Dictionary<String, ResourceLocator> resCache) + { + Contract.Requires(stream != null, "Need a stream!"); + Contract.Requires(stream.CanRead, "Stream should be readable!"); + Contract.Requires(resCache != null, "Need a Dictionary!"); + + _resCache = resCache; + _store = new BinaryReader(stream, Encoding.UTF8); + + _ums = stream as UnmanagedMemoryStream; + + BCLDebug.Log("RESMGRFILEFORMAT", "ResourceReader .ctor(Stream, Hashtable). UnmanagedMemoryStream: "+(_ums!=null)); + ReadResources(); + } + + + public void Close() + { + Dispose(true); + } + + public void Dispose() + { + Close(); + } + + [System.Security.SecuritySafeCritical] // auto-generated + private unsafe void Dispose(bool disposing) + { + if (_store != null) { + _resCache = null; + if (disposing) { + // Close the stream in a thread-safe way. This fix means + // that we may call Close n times, but that's safe. + BinaryReader copyOfStore = _store; + _store = null; + if (copyOfStore != null) + copyOfStore.Close(); + } + _store = null; + _namePositions = null; + _nameHashes = null; + _ums = null; + _namePositionsPtr = null; + _nameHashesPtr = null; + } + } + + [System.Security.SecurityCritical] // auto-generated + internal static unsafe int ReadUnalignedI4(int* p) + { + byte* buffer = (byte*)p; + // Unaligned, little endian format + return buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24); + } + + private void SkipInt32() { + _store.BaseStream.Seek(4, SeekOrigin.Current); + } + + + private void SkipString() { + int stringLength = _store.Read7BitEncodedInt(); + if (stringLength < 0) { + throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_NegativeStringLength")); + } + _store.BaseStream.Seek(stringLength, SeekOrigin.Current); + } + + [System.Security.SecuritySafeCritical] // auto-generated + private unsafe int GetNameHash(int index) + { + Contract.Assert(index >=0 && index < _numResources, "Bad index into hash array. index: "+index); + Contract.Assert((_ums == null && _nameHashes != null && _nameHashesPtr == null) || + (_ums != null && _nameHashes == null && _nameHashesPtr != null), "Internal state mangled."); + if (_ums == null) + return _nameHashes[index]; + else + return ReadUnalignedI4(&_nameHashesPtr[index]); + } + + [System.Security.SecuritySafeCritical] // auto-generated + private unsafe int GetNamePosition(int index) + { + Contract.Assert(index >=0 && index < _numResources, "Bad index into name position array. index: "+index); + Contract.Assert((_ums == null && _namePositions != null && _namePositionsPtr == null) || + (_ums != null && _namePositions == null && _namePositionsPtr != null), "Internal state mangled."); + int r; + if (_ums == null) + r = _namePositions[index]; + else + r = ReadUnalignedI4(&_namePositionsPtr[index]); + if (r < 0 || r > _dataSectionOffset - _nameSectionOffset) { + throw new FormatException(Environment.GetResourceString("BadImageFormat_ResourcesNameInvalidOffset", r)); + } + return r; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public IDictionaryEnumerator GetEnumerator() + { + if (_resCache == null) + throw new InvalidOperationException(Environment.GetResourceString("ResourceReaderIsClosed")); + return new ResourceEnumerator(this); + } + + internal ResourceEnumerator GetEnumeratorInternal() + { + return new ResourceEnumerator(this); + } + + // From a name, finds the associated virtual offset for the data. + // To read the data, seek to _dataSectionOffset + dataPos, then + // read the resource type & data. + // This does a binary search through the names. + internal int FindPosForResource(String name) + { + Contract.Assert(_store != null, "ResourceReader is closed!"); + int hash = FastResourceComparer.HashFunction(name); + BCLDebug.Log("RESMGRFILEFORMAT", "FindPosForResource for "+name+" hash: "+hash.ToString("x", CultureInfo.InvariantCulture)); + // Binary search over the hashes. Use the _namePositions array to + // determine where they exist in the underlying stream. + int lo = 0; + int hi = _numResources - 1; + int index = -1; + bool success = false; + while (lo <= hi) { + index = (lo + hi) >> 1; + // Do NOT use subtraction here, since it will wrap for large + // negative numbers. + int currentHash = GetNameHash(index); + int c; + if (currentHash == hash) + c = 0; + else if (currentHash < hash) + c = -1; + else + c = 1; + //BCLDebug.Log("RESMGRFILEFORMAT", " Probing index "+index+" lo: "+lo+" hi: "+hi+" c: "+c); + if (c == 0) { + success = true; + break; + } + if (c < 0) + lo = index + 1; + else + hi = index - 1; + } + if (!success) { +#if RESOURCE_FILE_FORMAT_DEBUG + String lastReadString; + lock(this) { + _store.BaseStream.Seek(_nameSectionOffset + GetNamePosition(index), SeekOrigin.Begin); + lastReadString = _store.ReadString(); + } + BCLDebug.Log("RESMGRFILEFORMAT", LogLevel.Status, "FindPosForResource for ", name, " failed. i: ", index, " lo: ", lo, " hi: ", hi, " last read string: \"", lastReadString, '\''); +#endif + return -1; + } + + // index is the location in our hash array that corresponds with a + // value in the namePositions array. + // There could be collisions in our hash function. Check on both sides + // of index to find the range of hash values that are equal to the + // target hash value. + if (lo != index) { + lo = index; + while (lo > 0 && GetNameHash(lo - 1) == hash) + lo--; + } + if (hi != index) { + hi = index; + while (hi < _numResources - 1 && GetNameHash(hi + 1) == hash) + hi++; + } + + lock(this) { + for(int i = lo; i<=hi; i++) { + _store.BaseStream.Seek(_nameSectionOffset + GetNamePosition(i), SeekOrigin.Begin); + if (CompareStringEqualsName(name)) { + int dataPos = _store.ReadInt32(); + if (dataPos < 0 || dataPos >= _store.BaseStream.Length - _dataSectionOffset) { + throw new FormatException(Environment.GetResourceString("BadImageFormat_ResourcesDataInvalidOffset", dataPos)); + } + return dataPos; + } + } + } + BCLDebug.Log("RESMGRFILEFORMAT", "FindPosForResource for "+name+": Found a hash collision, HOWEVER, neither of these collided values equaled the given string."); + return -1; + } + + // This compares the String in the .resources file at the current position + // with the string you pass in. + // Whoever calls this method should make sure that they take a lock + // so no one else can cause us to seek in the stream. + [System.Security.SecuritySafeCritical] // auto-generated + private unsafe bool CompareStringEqualsName(String name) + { + Contract.Assert(_store != null, "ResourceReader is closed!"); + int byteLen = _store.Read7BitEncodedInt(); + if (byteLen < 0) { + throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_NegativeStringLength")); + } + if (_ums != null) { + byte* bytes = _ums.PositionPointer; + // Skip over the data in the Stream, positioning ourselves right after it. + _ums.Seek(byteLen, SeekOrigin.Current); + if (_ums.Position > _ums.Length) { + throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_ResourcesNameTooLong")); + } + + // On 64-bit machines, these char*'s may be misaligned. Use a + // byte-by-byte comparison instead. + //return FastResourceComparer.CompareOrdinal((char*)bytes, byteLen/2, name) == 0; + return FastResourceComparer.CompareOrdinal(bytes, byteLen, name) == 0; + } + else { + // This code needs to be fast + byte[] bytes = new byte[byteLen]; + int numBytesToRead = byteLen; + while(numBytesToRead > 0) { + int n = _store.Read(bytes, byteLen - numBytesToRead, numBytesToRead); + if (n == 0) + throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_ResourceNameCorrupted")); + numBytesToRead -= n; + } + return FastResourceComparer.CompareOrdinal(bytes, byteLen/2, name) == 0; + } + } + + // This is used in the enumerator. The enumerator iterates from 0 to n + // of our resources and this returns the resource name for a particular + // index. The parameter is NOT a virtual offset. + [System.Security.SecurityCritical] // auto-generated + private unsafe String AllocateStringForNameIndex(int index, out int dataOffset) + { + Contract.Assert(_store != null, "ResourceReader is closed!"); + byte[] bytes; + int byteLen; + long nameVA = GetNamePosition(index); + lock (this) { + _store.BaseStream.Seek(nameVA + _nameSectionOffset, SeekOrigin.Begin); + // Can't use _store.ReadString, since it's using UTF-8! + byteLen = _store.Read7BitEncodedInt(); + if (byteLen < 0) { + throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_NegativeStringLength")); + } + + if (_ums != null) { + if (_ums.Position > _ums.Length - byteLen) + throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_ResourcesIndexTooLong", index)); + + String s = null; + char* charPtr = (char*)_ums.PositionPointer; +#if IA64 + if (((int)charPtr & 1) != 0) { + char[] destArray = new char[byteLen/2]; + fixed(char* pDest = destArray) { + Buffer.Memcpy((byte*)pDest, (byte*)charPtr, byteLen); + } + s = new String(destArray); + } + else { +#endif //IA64 + s = new String(charPtr, 0, byteLen/2); +#if IA64 + } +#endif //IA64 + _ums.Position += byteLen; + dataOffset = _store.ReadInt32(); + if (dataOffset < 0 || dataOffset >= _store.BaseStream.Length - _dataSectionOffset) { + throw new FormatException(Environment.GetResourceString("BadImageFormat_ResourcesDataInvalidOffset", dataOffset)); + } + return s; + } + + bytes = new byte[byteLen]; + // We must read byteLen bytes, or we have a corrupted file. + // Use a blocking read in case the stream doesn't give us back + // everything immediately. + int count = byteLen; + while(count > 0) { + int n = _store.Read(bytes, byteLen - count, count); + if (n == 0) + throw new EndOfStreamException(Environment.GetResourceString("BadImageFormat_ResourceNameCorrupted_NameIndex", index)); + count -= n; + } + dataOffset = _store.ReadInt32(); + if (dataOffset < 0 || dataOffset >= _store.BaseStream.Length - _dataSectionOffset) { + throw new FormatException(Environment.GetResourceString("BadImageFormat_ResourcesDataInvalidOffset", dataOffset)); + } + } + return Encoding.Unicode.GetString(bytes, 0, byteLen); + } + + // This is used in the enumerator. The enumerator iterates from 0 to n + // of our resources and this returns the resource value for a particular + // index. The parameter is NOT a virtual offset. + private Object GetValueForNameIndex(int index) + { + Contract.Assert(_store != null, "ResourceReader is closed!"); + long nameVA = GetNamePosition(index); + lock(this) { + _store.BaseStream.Seek(nameVA + _nameSectionOffset, SeekOrigin.Begin); + SkipString(); + //BCLDebug.Log("RESMGRFILEFORMAT", "GetValueForNameIndex for index: "+index+" skip (name length): "+skip); + int dataPos = _store.ReadInt32(); + if (dataPos < 0 || dataPos >= _store.BaseStream.Length - _dataSectionOffset) { + throw new FormatException(Environment.GetResourceString("BadImageFormat_ResourcesDataInvalidOffset", dataPos)); + } + BCLDebug.Log("RESMGRFILEFORMAT", "GetValueForNameIndex: dataPos: "+dataPos); + ResourceTypeCode junk; + if (_version == 1) + return LoadObjectV1(dataPos); + else + return LoadObjectV2(dataPos, out junk); + } + } + + // This takes a virtual offset into the data section and reads a String + // from that location. + // Anyone who calls LoadObject should make sure they take a lock so + // no one can cause us to do a seek in here. + internal String LoadString(int pos) + { + Contract.Assert(_store != null, "ResourceReader is closed!"); + _store.BaseStream.Seek(_dataSectionOffset+pos, SeekOrigin.Begin); + String s = null; + int typeIndex = _store.Read7BitEncodedInt(); + if (_version == 1) { + if (typeIndex == -1) + return null; + if (FindType(typeIndex) != typeof(String)) + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ResourceNotString_Type", FindType(typeIndex).FullName)); + s = _store.ReadString(); + } + else { + ResourceTypeCode typeCode = (ResourceTypeCode) typeIndex; + if (typeCode != ResourceTypeCode.String && typeCode != ResourceTypeCode.Null) { + String typeString; + if (typeCode < ResourceTypeCode.StartOfUserTypes) + typeString = typeCode.ToString(); + else + typeString = FindType(typeCode - ResourceTypeCode.StartOfUserTypes).FullName; + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ResourceNotString_Type", typeString)); + } + if (typeCode == ResourceTypeCode.String) // ignore Null + s = _store.ReadString(); + } + BCLDebug.Log("RESMGRFILEFORMAT", "LoadString("+pos.ToString("x", CultureInfo.InvariantCulture)+" returned "+(s==null ? "[a null string]" : s)); + return s; + } + + // Called from RuntimeResourceSet + internal Object LoadObject(int pos) + { + if (_version == 1) + return LoadObjectV1(pos); + ResourceTypeCode typeCode; + return LoadObjectV2(pos, out typeCode); + } + + internal Object LoadObject(int pos, out ResourceTypeCode typeCode) + { + if (_version == 1) { + Object o = LoadObjectV1(pos); + typeCode = (o is String) ? ResourceTypeCode.String : ResourceTypeCode.StartOfUserTypes; + return o; + } + return LoadObjectV2(pos, out typeCode); + } + + // This takes a virtual offset into the data section and reads an Object + // from that location. + // Anyone who calls LoadObject should make sure they take a lock so + // no one can cause us to do a seek in here. + internal Object LoadObjectV1(int pos) + { + Contract.Assert(_store != null, "ResourceReader is closed!"); + Contract.Assert(_version == 1, ".resources file was not a V1 .resources file!"); + + try { + // mega try-catch performs exceptionally bad on x64; factored out body into + // _LoadObjectV1 and wrap here. + return _LoadObjectV1(pos); + } + catch (EndOfStreamException eof) { + throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_TypeMismatch"), eof); + } + catch (ArgumentOutOfRangeException e) { + throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_TypeMismatch"), e); + } + } + +#if FEATURE_SERIALIZATION + [SecuritySafeCritical] +#endif + private Object _LoadObjectV1(int pos) { + _store.BaseStream.Seek(_dataSectionOffset+pos, SeekOrigin.Begin); + int typeIndex = _store.Read7BitEncodedInt(); + if (typeIndex == -1) + return null; + RuntimeType type = FindType(typeIndex); + BCLDebug.Log("RESMGRFILEFORMAT", "LoadObject type: "+type.Name+" pos: 0x"+_store.BaseStream.Position.ToString("x", CultureInfo.InvariantCulture)); + // Consider putting in logic to see if this type is a + // primitive or a value type first, so we can reach the + // deserialization code faster for arbitrary objects. + + if (type == typeof(String)) + return _store.ReadString(); + else if (type == typeof(Int32)) + return _store.ReadInt32(); + else if (type == typeof(Byte)) + return _store.ReadByte(); + else if (type == typeof(SByte)) + return _store.ReadSByte(); + else if (type == typeof(Int16)) + return _store.ReadInt16(); + else if (type == typeof(Int64)) + return _store.ReadInt64(); + else if (type == typeof(UInt16)) + return _store.ReadUInt16(); + else if (type == typeof(UInt32)) + return _store.ReadUInt32(); + else if (type == typeof(UInt64)) + return _store.ReadUInt64(); + else if (type == typeof(Single)) + return _store.ReadSingle(); + else if (type == typeof(Double)) + return _store.ReadDouble(); + else if (type == typeof(DateTime)) { + // Ideally we should use DateTime's ToBinary & FromBinary, + // but we can't for compatibility reasons. + return new DateTime(_store.ReadInt64()); + } + else if (type == typeof(TimeSpan)) + return new TimeSpan(_store.ReadInt64()); + else if (type == typeof(Decimal)) { + int[] bits = new int[4]; + for(int i=0; i<bits.Length; i++) + bits[i] = _store.ReadInt32(); + return new Decimal(bits); + } + else { +#if FEATURE_SERIALIZATION + return DeserializeObject(typeIndex); +#else + throw new NotSupportedException(Environment.GetResourceString("NotSupported_ResourceObjectSerialization")); +#endif // FEATURE_SERIALIZATION + } + } + + internal Object LoadObjectV2(int pos, out ResourceTypeCode typeCode) + { + Contract.Assert(_store != null, "ResourceReader is closed!"); + Contract.Assert(_version >= 2, ".resources file was not a V2 (or higher) .resources file!"); + + try { + // mega try-catch performs exceptionally bad on x64; factored out body into + // _LoadObjectV2 and wrap here. + return _LoadObjectV2(pos, out typeCode); + } + catch (EndOfStreamException eof) { + throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_TypeMismatch"), eof); + } + catch (ArgumentOutOfRangeException e) { + throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_TypeMismatch"), e); + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + private Object _LoadObjectV2(int pos, out ResourceTypeCode typeCode) { + _store.BaseStream.Seek(_dataSectionOffset+pos, SeekOrigin.Begin); + typeCode = (ResourceTypeCode) _store.Read7BitEncodedInt(); + + BCLDebug.Log("RESMGRFILEFORMAT", "LoadObjectV2 type: "+typeCode+" pos: 0x"+_store.BaseStream.Position.ToString("x", CultureInfo.InvariantCulture)); + + switch(typeCode) { + case ResourceTypeCode.Null: + return null; + + case ResourceTypeCode.String: + return _store.ReadString(); + + case ResourceTypeCode.Boolean: + return _store.ReadBoolean(); + + case ResourceTypeCode.Char: + return (char) _store.ReadUInt16(); + + case ResourceTypeCode.Byte: + return _store.ReadByte(); + + case ResourceTypeCode.SByte: + return _store.ReadSByte(); + + case ResourceTypeCode.Int16: + return _store.ReadInt16(); + + case ResourceTypeCode.UInt16: + return _store.ReadUInt16(); + + case ResourceTypeCode.Int32: + return _store.ReadInt32(); + + case ResourceTypeCode.UInt32: + return _store.ReadUInt32(); + + case ResourceTypeCode.Int64: + return _store.ReadInt64(); + + case ResourceTypeCode.UInt64: + return _store.ReadUInt64(); + + case ResourceTypeCode.Single: + return _store.ReadSingle(); + + case ResourceTypeCode.Double: + return _store.ReadDouble(); + + case ResourceTypeCode.Decimal: + return _store.ReadDecimal(); + + case ResourceTypeCode.DateTime: + // Use DateTime's ToBinary & FromBinary. + Int64 data = _store.ReadInt64(); + return DateTime.FromBinary(data); + + case ResourceTypeCode.TimeSpan: + Int64 ticks = _store.ReadInt64(); + return new TimeSpan(ticks); + + // Special types + case ResourceTypeCode.ByteArray: { + int len = _store.ReadInt32(); + if (len < 0) { + throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_ResourceDataLengthInvalid", len)); + } + + if (_ums == null) { + if (len > _store.BaseStream.Length) { + throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_ResourceDataLengthInvalid", len)); + } + return _store.ReadBytes(len); + } + + if (len > _ums.Length - _ums.Position) { + throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_ResourceDataLengthInvalid", len)); + } + + byte[] bytes = new byte[len]; + int r = _ums.Read(bytes, 0, len); + Contract.Assert(r == len, "ResourceReader needs to use a blocking read here. (Call _store.ReadBytes(len)?)"); + return bytes; + } + + case ResourceTypeCode.Stream: { + int len = _store.ReadInt32(); + if (len < 0) { + throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_ResourceDataLengthInvalid", len)); + } + if (_ums == null) { + byte[] bytes = _store.ReadBytes(len); + // Lifetime of memory == lifetime of this stream. + return new PinnedBufferMemoryStream(bytes); + } + + // make sure we don't create an UnmanagedMemoryStream that is longer than the resource stream. + if (len > _ums.Length - _ums.Position) { + throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_ResourceDataLengthInvalid", len)); + } + + // For the case that we've memory mapped in the .resources + // file, just return a Stream pointing to that block of memory. + unsafe { + return new UnmanagedMemoryStream(_ums.PositionPointer, len, len, FileAccess.Read, true); + } + } + + default: + if (typeCode < ResourceTypeCode.StartOfUserTypes) { + throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_TypeMismatch")); + } + break; + } + + // Normal serialized objects +#if FEATURE_SERIALIZATION + int typeIndex = typeCode - ResourceTypeCode.StartOfUserTypes; + return DeserializeObject(typeIndex); +#else + throw new NotSupportedException(Environment.GetResourceString("NotSupported_ResourceObjectSerialization")); +#endif // FEATURE_SERIALIZATION + } + + +#if FEATURE_SERIALIZATION + // Helper method to safely deserialize a type, using a type-limiting + // deserialization binder to simulate a type-limiting deserializer. + // This method handles types that are safe to deserialize, as well as + // ensuring we only get back what we expect. + [System.Security.SecurityCritical] // auto-generated + private Object DeserializeObject(int typeIndex) + { + RuntimeType type = FindType(typeIndex); + // Initialize deserialization permission array, if needed + if (_safeToDeserialize == null) + InitSafeToDeserializeArray(); + + // Ensure that the object we deserialized is exactly the same + // type of object we thought we should be deserializing. This + // will help prevent malformed .resources files from using our + // serialization permission assert to deserialize anything + // via a malformed type ID. + + Object graph; + if (_safeToDeserialize[typeIndex]) { + // Don't assert serialization permission - just ask the binary + // formatter to avoid a permission check. This ensures that any + // types which do demand serialization permission in their + // deserialization .cctors will fail. + // Also, use a serialization binder to limit bind requests to + // our allowed list of WinForms types. + _objFormatter.Binder = _typeLimitingBinder; + _typeLimitingBinder.ExpectingToDeserialize(type); + graph = _objFormatter.UnsafeDeserialize(_store.BaseStream, null); + } + else { + _objFormatter.Binder = null; + graph = _objFormatter.Deserialize(_store.BaseStream); + } + + // This check is about correctness, not security at this point. + if (graph.GetType() != type) + throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_ResType&SerBlobMismatch", type.FullName, graph.GetType().FullName)); + + return graph; + } +#endif // FEATURE_SERIALIZATION + + // Reads in the header information for a .resources file. Verifies some + // of the assumptions about this resource set, and builds the class table + // for the default resource file format. + [System.Security.SecurityCritical] // auto-generated + private void ReadResources() + { + Contract.Assert(_store != null, "ResourceReader is closed!"); +#if FEATURE_SERIALIZATION + BinaryFormatter bf = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File | StreamingContextStates.Persistence)); + _typeLimitingBinder = new TypeLimitingDeserializationBinder(); + bf.Binder = _typeLimitingBinder; + _objFormatter = bf; +#endif // FEATURE_SERIALIZATION + + try { + // mega try-catch performs exceptionally bad on x64; factored out body into + // _ReadResources and wrap here. + _ReadResources(); + } + catch (EndOfStreamException eof) { + throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_ResourcesHeaderCorrupted"), eof); + } + catch (IndexOutOfRangeException e) { + throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_ResourcesHeaderCorrupted"), e); + } + } + + [System.Security.SecurityCritical] // auto-generated + private void _ReadResources() + { + // Read ResourceManager header + // Check for magic number + int magicNum = _store.ReadInt32(); + if (magicNum != ResourceManager.MagicNumber) + throw new ArgumentException(Environment.GetResourceString("Resources_StreamNotValid")); + // Assuming this is ResourceManager header V1 or greater, hopefully + // after the version number there is a number of bytes to skip + // to bypass the rest of the ResMgr header. For V2 or greater, we + // use this to skip to the end of the header + int resMgrHeaderVersion = _store.ReadInt32(); + int numBytesToSkip = _store.ReadInt32(); + if (numBytesToSkip < 0 || resMgrHeaderVersion < 0) { + throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_ResourcesHeaderCorrupted")); + } + if (resMgrHeaderVersion > 1) { + BCLDebug.Log("RESMGRFILEFORMAT", LogLevel.Status, "ReadResources: Unexpected ResMgr header version: {0} Skipping ahead {1} bytes.", resMgrHeaderVersion, numBytesToSkip); + _store.BaseStream.Seek(numBytesToSkip, SeekOrigin.Current); + } + else { + BCLDebug.Log("RESMGRFILEFORMAT", "ReadResources: Parsing ResMgr header v1."); + // We don't care about numBytesToSkip; read the rest of the header + + // Read in type name for a suitable ResourceReader + // Note ResourceWriter & InternalResGen use different Strings. + String readerType = _store.ReadString(); + readerType = System.CoreLib.FixupCoreLibName(readerType); + AssemblyName mscorlib = new AssemblyName(ResourceManager.MscorlibName); + + if (!ResourceManager.CompareNames(readerType, ResourceManager.ResReaderTypeName, mscorlib)) + throw new NotSupportedException(Environment.GetResourceString("NotSupported_WrongResourceReader_Type", readerType)); + + // Skip over type name for a suitable ResourceSet + SkipString(); + } + + // Read RuntimeResourceSet header + // Do file version check + int version = _store.ReadInt32(); + if (version != RuntimeResourceSet.Version && version != 1) + throw new ArgumentException(Environment.GetResourceString("Arg_ResourceFileUnsupportedVersion", RuntimeResourceSet.Version, version)); + _version = version; + +#if RESOURCE_FILE_FORMAT_DEBUG + // Look for ***DEBUG*** to see if this is a debuggable file. + long oldPos = _store.BaseStream.Position; + _debug = false; + try { + String debugString = _store.ReadString(); + _debug = String.Equals("***DEBUG***", debugString); + } + catch(IOException) { + } + catch(OutOfMemoryException) { + } + if (_debug) { + Console.WriteLine("ResourceReader is looking at a debuggable .resources file, version {0}", _version); + } + else { + _store.BaseStream.Position = oldPos; + } +#endif + + _numResources = _store.ReadInt32(); + if (_numResources < 0) { + throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_ResourcesHeaderCorrupted")); + } + BCLDebug.Log("RESMGRFILEFORMAT", "ReadResources: Expecting " + _numResources + " resources."); +#if _DEBUG + if (ResourceManager.DEBUG >= 4) + Console.WriteLine("ResourceReader::ReadResources - Reading in "+_numResources+" resources"); +#endif + + // Read type positions into type positions array. + // But delay initialize the type table. + int numTypes = _store.ReadInt32(); + if (numTypes < 0) { + throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_ResourcesHeaderCorrupted")); + } + _typeTable = new RuntimeType[numTypes]; + _typeNamePositions = new int[numTypes]; + for (int i=0; i<numTypes; i++) { + _typeNamePositions[i] = (int) _store.BaseStream.Position; + + // Skip over the Strings in the file. Don't create types. + SkipString(); + } + +#if _DEBUG + if (ResourceManager.DEBUG >= 5) + Console.WriteLine("ResourceReader::ReadResources - Reading in "+numTypes+" type table entries"); +#endif + + // Prepare to read in the array of name hashes + // Note that the name hashes array is aligned to 8 bytes so + // we can use pointers into it on 64 bit machines. (4 bytes + // may be sufficient, but let's plan for the future) + // Skip over alignment stuff. All public .resources files + // should be aligned No need to verify the byte values. + long pos = _store.BaseStream.Position; + int alignBytes = ((int)pos) & 7; + if (alignBytes != 0) { + for (int i = 0; i < 8 - alignBytes; i++) { + _store.ReadByte(); + } + } + + // Read in the array of name hashes +#if RESOURCE_FILE_FORMAT_DEBUG + // Skip over "HASHES->" + if (_debug) { + _store.BaseStream.Position += 8; + } +#endif + + if (_ums == null) { + _nameHashes = new int[_numResources]; + for (int i = 0; i < _numResources; i++) { + _nameHashes[i] = _store.ReadInt32(); + } + } + else { + int seekPos = unchecked(4 * _numResources); + if (seekPos < 0) { + throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_ResourcesHeaderCorrupted")); + } + unsafe { + _nameHashesPtr = (int*)_ums.PositionPointer; + // Skip over the array of nameHashes. + _ums.Seek(seekPos, SeekOrigin.Current); + // get the position pointer once more to check that the whole table is within the stream + byte* junk = _ums.PositionPointer; + } + } + + // Read in the array of relative positions for all the names. +#if RESOURCE_FILE_FORMAT_DEBUG + // Skip over "POS---->" + if (_debug) { + _store.BaseStream.Position += 8; + } +#endif + if (_ums == null) { + _namePositions = new int[_numResources]; + for (int i = 0; i < _numResources; i++) { + int namePosition = _store.ReadInt32(); + if (namePosition < 0) { + throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_ResourcesHeaderCorrupted")); + } + + _namePositions[i] = namePosition; + } + } + else { + int seekPos = unchecked(4 * _numResources); + if (seekPos < 0) { + throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_ResourcesHeaderCorrupted")); + } + unsafe { + _namePositionsPtr = (int*)_ums.PositionPointer; + // Skip over the array of namePositions. + _ums.Seek(seekPos, SeekOrigin.Current); + // get the position pointer once more to check that the whole table is within the stream + byte* junk = _ums.PositionPointer; + } + } + + // Read location of data section. + _dataSectionOffset = _store.ReadInt32(); + if (_dataSectionOffset < 0) { + throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_ResourcesHeaderCorrupted")); + } + + // Store current location as start of name section + _nameSectionOffset = _store.BaseStream.Position; + + // _nameSectionOffset should be <= _dataSectionOffset; if not, it's corrupt + if (_dataSectionOffset < _nameSectionOffset) { + throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_ResourcesHeaderCorrupted")); + } + + BCLDebug.Log("RESMGRFILEFORMAT", String.Format(CultureInfo.InvariantCulture, "ReadResources: _nameOffset = 0x{0:x} _dataOffset = 0x{1:x}", _nameSectionOffset, _dataSectionOffset)); + } + + // This allows us to delay-initialize the Type[]. This might be a + // good startup time savings, since we might have to load assemblies + // and initialize Reflection. + private RuntimeType FindType(int typeIndex) + { + if (typeIndex < 0 || typeIndex >= _typeTable.Length) { + throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_InvalidType")); + } + if (_typeTable[typeIndex] == null) { + long oldPos = _store.BaseStream.Position; + try { + _store.BaseStream.Position = _typeNamePositions[typeIndex]; + String typeName = _store.ReadString(); + _typeTable[typeIndex] = (RuntimeType)Type.GetType(typeName, true); + } +#if !FEATURE_SERIALIZATION + // If serialization isn't supported, we convert FileNotFoundException to + // NotSupportedException for consistency with v2. This is a corner-case, but the + // idea is that we want to give the user a more accurate error message. Even if + // the dependency were found, we know it will require serialization since it + // can't be one of the types we special case. So if the dependency were found, + // it would go down the serialization code path, resulting in NotSupported for + // SKUs without serialization. + // + // We don't want to regress the expected case by checking the type info before + // getting to Type.GetType -- this is costly with v1 resource formats. + catch (FileNotFoundException) + { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_ResourceObjectSerialization")); + } +#endif // FEATURE_SERIALIZATION + finally { + _store.BaseStream.Position = oldPos; + } + } + Contract.Assert(_typeTable[typeIndex] != null, "Should have found a type!"); + return _typeTable[typeIndex]; + } + +#if FEATURE_SERIALIZATION + [System.Security.SecurityCritical] // auto-generated + private void InitSafeToDeserializeArray() + { + _safeToDeserialize = new bool[_typeTable.Length]; + for(int i=0; i<_typeTable.Length; i++) { + long oldPos = _store.BaseStream.Position; + String typeName; + try { + _store.BaseStream.Position = _typeNamePositions[i]; + typeName = _store.ReadString(); + } + finally { + _store.BaseStream.Position = oldPos; + } + + AssemblyName an; + String typePart; + RuntimeType resourceType = (RuntimeType)Type.GetType(typeName, false); + if (resourceType == null) { + an = null; + typePart = typeName; + } + else { + // Enums should be safe to deserialize, and this helps out + // partially trusted, localized WinForms apps. + if (resourceType.BaseType == typeof(Enum)) { + _safeToDeserialize[i] = true; + continue; + } + + // For most types, check our TypesSafeForDeserialization. + typePart = resourceType.FullName; + + an = new AssemblyName(); + + // resourceType is retrieved via Type.GetType and must be a RuntimeType + RuntimeAssembly a = (RuntimeAssembly)resourceType.Assembly; + an.Init(a.GetSimpleName(), + a.GetPublicKey(), + null, // public key token + null, // version + a.GetLocale(), + AssemblyHashAlgorithm.None, + AssemblyVersionCompatibility.SameMachine, + null, // codebase + AssemblyNameFlags.PublicKey, + null); // strong name key pair + } + + foreach(String safeType in TypesSafeForDeserialization) { + if (ResourceManager.CompareNames(safeType, typePart, an)) { +#if _DEBUG + if (ResourceManager.DEBUG >= 7) + Console.WriteLine("ResReader: Found a type-safe type to deserialize! Type name: {0}", typeName); +#endif + _safeToDeserialize[i] = true; + continue; + } + } + +#if _DEBUG + if (ResourceManager.DEBUG >= 7) + if (!_safeToDeserialize[i]) + Console.WriteLine("ResReader: Found a type that wasn't safe to deserialize: {0}", typeName); +#endif + } + } +#endif // FEATURE_SERIALIZATION + + public void GetResourceData(String resourceName, out String resourceType, out byte[] resourceData) + { + if (resourceName == null) + throw new ArgumentNullException("resourceName"); + Contract.EndContractBlock(); + if (_resCache == null) + throw new InvalidOperationException(Environment.GetResourceString("ResourceReaderIsClosed")); + + // Get the type information from the data section. Also, + // sort all of the data section's indexes to compute length of + // the serialized data for this type (making sure to subtract + // off the length of the type code). + int[] sortedDataPositions = new int[_numResources]; + int dataPos = FindPosForResource(resourceName); + if( dataPos == -1) { + throw new ArgumentException(Environment.GetResourceString("Arg_ResourceNameNotExist", resourceName)); + } + + lock(this) { + // Read all the positions of data within the data section. + for(int i=0; i<_numResources; i++) { + _store.BaseStream.Position = _nameSectionOffset + GetNamePosition(i); + // Skip over name of resource + int numBytesToSkip = _store.Read7BitEncodedInt(); + if (numBytesToSkip < 0) { + throw new FormatException(Environment.GetResourceString("BadImageFormat_ResourcesNameInvalidOffset", numBytesToSkip)); + } + _store.BaseStream.Position += numBytesToSkip; + + int dPos = _store.ReadInt32(); + if (dPos < 0 || dPos >= _store.BaseStream.Length - _dataSectionOffset) { + throw new FormatException(Environment.GetResourceString("BadImageFormat_ResourcesDataInvalidOffset", dPos)); + } + sortedDataPositions[i] = dPos; + } + Array.Sort(sortedDataPositions); + + int index = Array.BinarySearch(sortedDataPositions, dataPos); + Contract.Assert(index >= 0 && index < _numResources, "Couldn't find data position within sorted data positions array!"); + long nextData = (index < _numResources - 1) ? sortedDataPositions[index + 1] + _dataSectionOffset : _store.BaseStream.Length; + int len = (int) (nextData - (dataPos + _dataSectionOffset)); + Contract.Assert(len >= 0 && len <= (int) _store.BaseStream.Length - dataPos + _dataSectionOffset, "Length was negative or outside the bounds of the file!"); + + // Read type code then byte[] + _store.BaseStream.Position = _dataSectionOffset + dataPos; + ResourceTypeCode typeCode = (ResourceTypeCode) _store.Read7BitEncodedInt(); + if (typeCode < 0 || typeCode >= ResourceTypeCode.StartOfUserTypes + _typeTable.Length) { + throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_InvalidType")); + } + resourceType = TypeNameFromTypeCode(typeCode); + + // The length must be adjusted to subtract off the number + // of bytes in the 7 bit encoded type code. + len -= (int) (_store.BaseStream.Position - (_dataSectionOffset + dataPos)); + byte[] bytes = _store.ReadBytes(len); + if (bytes.Length != len) + throw new FormatException(Environment.GetResourceString("BadImageFormat_ResourceNameCorrupted")); + resourceData = bytes; + } + } + + private String TypeNameFromTypeCode(ResourceTypeCode typeCode) + { + Contract.Requires(typeCode >= 0, "can't be negative"); + if (typeCode < ResourceTypeCode.StartOfUserTypes) { + Contract.Assert(!String.Equals(typeCode.ToString(), "LastPrimitive"), "Change ResourceTypeCode metadata order so LastPrimitive isn't what Enum.ToString prefers."); + return "ResourceTypeCode." + typeCode.ToString(); + } + else { + int typeIndex = typeCode - ResourceTypeCode.StartOfUserTypes; + Contract.Assert(typeIndex >= 0 && typeIndex < _typeTable.Length, "TypeCode is broken or corrupted!"); + long oldPos = _store.BaseStream.Position; + try { + _store.BaseStream.Position = _typeNamePositions[typeIndex]; + return _store.ReadString(); + } + finally { + _store.BaseStream.Position = oldPos; + } + } + } + +#if FEATURE_SERIALIZATION + // We need to build a type-limiting deserializer. We know exactly which + // type we want to deserialize, and if someone tells us we have type X + // (which might be safe to deserialize) and they give us a serialized + // form of type Y, we don't want to run the deserialization constructor + // because we've asserted serialization formatter permission. Instead, + // limit the binary formatter's type binding to precisely the type we + // expect. If they ever don't match, that's a corrupt .resources file. + // We also must check the complete object graph to ensure all of the + // graph contains safe objects. + // Note this is tightly coupled to the BinaryFormatter, since we use + // its internal ObjectReader::FastBindToType method, and we had to + // change the ObjectReader to register itself with this type. + internal sealed class TypeLimitingDeserializationBinder : SerializationBinder + { + private RuntimeType _typeToDeserialize; + // This is tightly coupled with the binary formatter, because we + // want to use exactly the same code found in the ObjectReader + // to do the lookup, then just give a thumbs up or down based on + // a type equality comparison. In the future, we could consider + // some better refactoring of this code. + private ObjectReader _objectReader; + + internal ObjectReader ObjectReader { + get { return _objectReader; } + set { _objectReader = value; } + } + + internal void ExpectingToDeserialize(RuntimeType type) + { + _typeToDeserialize = type; + } + + [System.Security.SecuritySafeCritical] // overrides transparent public member + public override Type BindToType(string assemblyName, string typeName) + { + // BinaryObjectReader::Bind tries us first, then its own code. + // Returning null means let the default binding rules happen. + AssemblyName an = new AssemblyName(assemblyName); + + bool safe = false; + foreach(String safeType in TypesSafeForDeserialization) { + if (ResourceManager.CompareNames(safeType, typeName, an)) { + safe = true; + break; + } + } + + // WinForms types may internally use some enums that aren't + // on our safe to deserialize list, like Font using FontStyle. + Type t = ObjectReader.FastBindToType(assemblyName, typeName); + if (t.IsEnum) + safe = true; + + if (safe) + return null; + + // Throw instead of returning null. + // If you're looking at this in a debugger, you've either + // got a malformed .resources file on your hands, or WinForms + // types have taken a new dependency on another type. Check + // whether assemblyName & typeName refer to a trustworthy type, + // & consider adding it to the TypesSafeToDeserialize list. + throw new BadImageFormatException(Environment.GetResourceString("BadImageFormat_ResType&SerBlobMismatch", _typeToDeserialize.FullName, typeName)); + } + } +#endif // FEATURE_SERIALIZATION + + + internal sealed class ResourceEnumerator : IDictionaryEnumerator + { + private const int ENUM_DONE = Int32.MinValue; + private const int ENUM_NOT_STARTED = -1; + + private ResourceReader _reader; + private bool _currentIsValid; + private int _currentName; + private int _dataPosition; // cached for case-insensitive table + + internal ResourceEnumerator(ResourceReader reader) + { + _currentName = ENUM_NOT_STARTED; + _reader = reader; + _dataPosition = -2; + } + + public bool MoveNext() + { + if (_currentName == _reader._numResources - 1 || _currentName == ENUM_DONE) { + _currentIsValid = false; + _currentName = ENUM_DONE; + return false; + } + _currentIsValid = true; + _currentName++; + return true; + } + + public Object Key { + [System.Security.SecuritySafeCritical] // auto-generated + get { + if (_currentName == ENUM_DONE) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumEnded)); + if (!_currentIsValid) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumNotStarted)); + if (_reader._resCache == null) throw new InvalidOperationException(Environment.GetResourceString("ResourceReaderIsClosed")); + + return _reader.AllocateStringForNameIndex(_currentName, out _dataPosition); + } + } + + public Object Current { + get { + return Entry; + } + } + + // Warning: This requires that you call the Key or Entry property FIRST before calling it! + internal int DataPosition { + get { + return _dataPosition; + } + } + + public DictionaryEntry Entry { + [System.Security.SecuritySafeCritical] // auto-generated + get { + if (_currentName == ENUM_DONE) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumEnded)); + if (!_currentIsValid) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumNotStarted)); + if (_reader._resCache == null) throw new InvalidOperationException(Environment.GetResourceString("ResourceReaderIsClosed")); + + String key; + Object value = null; + lock (_reader) { // locks should be taken in the same order as in RuntimeResourceSet.GetObject to avoid deadlock + lock (_reader._resCache) { + key = _reader.AllocateStringForNameIndex(_currentName, out _dataPosition); // AllocateStringForNameIndex could lock on _reader + ResourceLocator locator; + if (_reader._resCache.TryGetValue(key, out locator)) { + value = locator.Value; + } + if (value == null) { + if (_dataPosition == -1) + value = _reader.GetValueForNameIndex(_currentName); + else + value = _reader.LoadObject(_dataPosition); + // If enumeration and subsequent lookups happen very + // frequently in the same process, add a ResourceLocator + // to _resCache here. But WinForms enumerates and + // just about everyone else does lookups. So caching + // here may bloat working set. + } + } + } + return new DictionaryEntry(key, value); + } + } + + public Object Value { + get { + if (_currentName == ENUM_DONE) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumEnded)); + if (!_currentIsValid) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumNotStarted)); + if (_reader._resCache == null) throw new InvalidOperationException(Environment.GetResourceString("ResourceReaderIsClosed")); + + // Consider using _resCache here, eventually, if + // this proves to be an interesting perf scenario. + // But mixing lookups and enumerators shouldn't be + // particularly compelling. + return _reader.GetValueForNameIndex(_currentName); + } + } + + public void Reset() + { + if (_reader._resCache == null) throw new InvalidOperationException(Environment.GetResourceString("ResourceReaderIsClosed")); + _currentIsValid = false; + _currentName = ENUM_NOT_STARTED; + } + } + } +} |