1446 lines
67 KiB
C#
1446 lines
67 KiB
C#
// ==++==
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
// ==--==
|
|
/*============================================================
|
|
**
|
|
** Class: ResourceReader
|
|
**
|
|
** <OWNER>[....]</OWNER>
|
|
**
|
|
**
|
|
** 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_PAL && 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_PAL && FEATURE_SERIALIZATION
|
|
|
|
#if FEATURE_CORECLR
|
|
[System.Security.SecurityCritical] // auto-generated
|
|
#else
|
|
[System.Security.SecuritySafeCritical]
|
|
#endif
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
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
|
|
if (!BitConverter.IsLittleEndian) {
|
|
byte* bytePtr = (byte*) charPtr;
|
|
var dest = new byte[byteLen];
|
|
for (int i = 0; i < byteLen; i += 2) {
|
|
dest[i] = *(bytePtr+i+1);
|
|
dest[i+1] = *(bytePtr+i);
|
|
}
|
|
|
|
fixed(byte *pDest = dest) {
|
|
s = new String((char *)pDest, 0, byteLen/2);
|
|
}
|
|
} else
|
|
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);
|
|
#if !FEATURE_PAL
|
|
// Initialize deserialization permission array, if needed
|
|
if (_safeToDeserialize == null)
|
|
InitSafeToDeserializeArray();
|
|
#endif // !FEATURE_PAL
|
|
|
|
// Ensure that the object we deserialized is exactly the same
|
|
// type of object we thought we should be deserializing. This
|
|
// will help prevent hacked .resources files from using our
|
|
// serialization permission assert to deserialize anything
|
|
// via a hacked type ID.
|
|
|
|
Object graph;
|
|
#if FEATURE_PAL
|
|
graph = _objFormatter.Deserialize(_store.BaseStream);
|
|
#else
|
|
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 [....] types.
|
|
_objFormatter.Binder = _typeLimitingBinder;
|
|
_typeLimitingBinder.ExpectingToDeserialize(type);
|
|
graph = _objFormatter.UnsafeDeserialize(_store.BaseStream, null);
|
|
}
|
|
else {
|
|
_objFormatter.Binder = null;
|
|
graph = _objFormatter.Deserialize(_store.BaseStream);
|
|
}
|
|
#endif // FEATURE_PAL
|
|
|
|
// 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));
|
|
#if !FEATURE_PAL
|
|
_typeLimitingBinder = new TypeLimitingDeserializationBinder();
|
|
bf.Binder = _typeLimitingBinder;
|
|
#endif
|
|
_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();
|
|
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_PAL && 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 [....] 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_PAL && 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_PAL && 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;
|
|
}
|
|
}
|
|
|
|
// [....] 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 hacked .resources file on your hands, or [....]
|
|
// 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_PAL && 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._resCache) {
|
|
key = _reader.AllocateStringForNameIndex(_currentName, out _dataPosition);
|
|
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 [....] 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;
|
|
}
|
|
}
|
|
}
|
|
}
|