//
// System.Resources.ResourceReader.cs
//
// Authors:
// Duncan Mak <duncan@ximian.com>
// Nick Drochak <ndrochak@gol.com>
// Dick Porter <dick@ximian.com>
// Marek Safar <marek.safar@seznam.cz>
//
// (C) 2001, 2002 Ximian Inc, http://www.ximian.com
// Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System.Collections;
using System.Resources;
using System.IO;
using System.Text;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Permissions;
namespace System.Resources
{
internal enum PredefinedResourceType
{
Null = 0,
String = 1,
Bool = 2,
Char = 3,
Byte = 4,
SByte = 5,
Int16 = 6,
UInt16 = 7,
Int32 = 8,
UInt32 = 9,
Int64 = 10,
UInt64 = 11,
Single = 12,
Double = 13,
Decimal = 14,
DateTime = 15,
TimeSpan = 16,
ByteArray = 32,
Stream = 33,
FistCustom = 64
}
public sealed class ResourceReader : IResourceReader, IEnumerable, IDisposable
{
BinaryReader reader;
IFormatter formatter;
internal int resourceCount = 0;
int typeCount = 0;
Type[] types;
int[] hashes;
long[] positions;
int dataSectionOffset;
long nameSectionOffset;
int resource_ver;
// Constructors
[SecurityPermission (SecurityAction.LinkDemand, SerializationFormatter = true)]
public ResourceReader (Stream stream)
{
if (stream == null)
throw new ArgumentNullException ("Value cannot be null.");
if (!stream.CanRead)
throw new ArgumentException ("Stream was not readable.");
reader = new BinaryReader(stream, Encoding.UTF8);
formatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File|StreamingContextStates.Persistence));
ReadHeaders();
}
public ResourceReader (string fileName)
{
if (fileName == null)
throw new ArgumentNullException ("Path cannot be null.");
if (!System.IO.File.Exists (fileName))
throw new FileNotFoundException ("Could not find file " + Path.GetFullPath(fileName));
reader = new BinaryReader (new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read));
formatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File|StreamingContextStates.Persistence));
ReadHeaders();
}
/* Read the ResourceManager header and the
* ResourceReader header.
*/
private void ReadHeaders()
{
try {
int manager_magic = reader.ReadInt32();
if(manager_magic != ResourceManager.MagicNumber) {
throw new ArgumentException("Stream is not a valid .resources file!");
}
int manager_ver = reader.ReadInt32();
int manager_len = reader.ReadInt32();
/* We know how long the header is, even if
* the version number is too new
*/
if(manager_ver > ResourceManager.HeaderVersionNumber) {
reader.BaseStream.Seek(manager_len, SeekOrigin.Current);
} else {
string reader_class=reader.ReadString();
if(!reader_class.StartsWith("System.Resources.ResourceReader")) {
throw new NotSupportedException("This .resources file requires reader class " + reader_class);
}
string set_class=reader.ReadString();
if(!set_class.StartsWith(typeof(ResourceSet).FullName) && !set_class.StartsWith("System.Resources.RuntimeResourceSet")) {
throw new NotSupportedException("This .resources file requires set class " + set_class);
}
}
/* Now read the ResourceReader header */
resource_ver = reader.ReadInt32();
if(resource_ver != 1
#if NET_2_0
&& resource_ver != 2
#endif
) {
throw new NotSupportedException("This .resources file requires unsupported set class version: " + resource_ver.ToString());
}
resourceCount = reader.ReadInt32();
typeCount = reader.ReadInt32();
types=new Type[typeCount];
for(int i=0; i<typeCount; i++) {
types[i]=Type.GetType(reader.ReadString(), true);
}
/* There are between 0 and 7 bytes of
* padding here, consisting of the
* letters PAD. The next item (Hash
* values for each resource name) need
* to be aligned on an 8-byte
* boundary.
*/
int pad_align=(int)(reader.BaseStream.Position & 7);
int pad_chars=0;
if(pad_align!=0) {
pad_chars=8-pad_align;
}
for(int i=0; i<pad_chars; i++) {
byte pad_byte=reader.ReadByte();
if(pad_byte!="PAD"[i%3]) {
throw new ArgumentException("Malformed .resources file (padding values incorrect)");
}
}
/* Read in the hash values for each
* resource name. These can be used
* by ResourceSet (calling internal
* methods) to do a fast compare on
* resource names without doing
* expensive string compares (but we
* dont do that yet, so far we only
* implement the Enumerator interface)
*/
hashes=new int[resourceCount];
for(int i=0; i<resourceCount; i++) {
hashes[i]=reader.ReadInt32();
}
/* Read in the virtual offsets for
* each resource name
*/
positions=new long[resourceCount];
for(int i=0; i<resourceCount; i++) {
positions[i]=reader.ReadInt32();
}
dataSectionOffset = reader.ReadInt32();
nameSectionOffset = reader.BaseStream.Position;
} catch(EndOfStreamException e) {
throw new ArgumentException("Stream is not a valied .resources file! It was possibly truncated.", e);
}
}
/* Cut and pasted from BinaryReader, because it's
* 'protected' there
*/
private int Read7BitEncodedInt() {
int ret = 0;
int shift = 0;
byte b;
do {
b = reader.ReadByte();
ret = ret | ((b & 0x7f) << shift);
shift += 7;
} while ((b & 0x80) == 0x80);
return ret;
}
private string ResourceName(int index)
{
lock(this)
{
long pos=positions[index]+nameSectionOffset;
reader.BaseStream.Seek(pos, SeekOrigin.Begin);
/* Read a 7-bit encoded byte length field */
int len=Read7BitEncodedInt();
byte[] str=new byte[len];
reader.Read(str, 0, len);
return Encoding.Unicode.GetString(str);
}
}
// TODO: Read complex types
object ReadValueVer2 (int type_index)
{
switch ((PredefinedResourceType)type_index)
{
case PredefinedResourceType.Null:
return null;
case PredefinedResourceType.String:
return reader.ReadString();
case PredefinedResourceType.Bool:
return reader.ReadBoolean ();
case PredefinedResourceType.Char:
return (char)reader.ReadUInt16();
case PredefinedResourceType.Byte:
return reader.ReadByte();
case PredefinedResourceType.SByte:
return reader.ReadSByte();
case PredefinedResourceType.Int16:
return reader.ReadInt16();
case PredefinedResourceType.UInt16:
return reader.ReadUInt16();
case PredefinedResourceType.Int32:
return reader.ReadInt32();
case PredefinedResourceType.UInt32:
return reader.ReadUInt32();
case PredefinedResourceType.Int64:
return reader.ReadInt64();
case PredefinedResourceType.UInt64:
return reader.ReadUInt64();
case PredefinedResourceType.Single:
return reader.ReadSingle();
case PredefinedResourceType.Double:
return reader.ReadDouble();
case PredefinedResourceType.Decimal:
return reader.ReadDecimal();
case PredefinedResourceType.DateTime:
return new DateTime(reader.ReadInt64());
case PredefinedResourceType.TimeSpan:
return new TimeSpan(reader.ReadInt64());
case PredefinedResourceType.ByteArray:
throw new NotImplementedException (PredefinedResourceType.ByteArray.ToString ());
case PredefinedResourceType.Stream:
throw new NotImplementedException (PredefinedResourceType.Stream.ToString ());
}
type_index -= (int)PredefinedResourceType.FistCustom;
return ReadNonPredefinedValue (types[type_index]);
}
object ReadValueVer1 (Type type)
{
// The most common first
if (type==typeof(String))
return reader.ReadString();
if (type==typeof(Int32))
return reader.ReadInt32();
if (type==typeof(Byte))
return(reader.ReadByte());
if (type==typeof(Double))
return(reader.ReadDouble());
if (type==typeof(Int16))
return(reader.ReadInt16());
if (type==typeof(Int64))
return(reader.ReadInt64());
if (type==typeof(SByte))
return(reader.ReadSByte());
if (type==typeof(Single))
return(reader.ReadSingle());
if (type==typeof(TimeSpan))
return(new TimeSpan(reader.ReadInt64()));
if (type==typeof(UInt16))
return(reader.ReadUInt16());
if (type==typeof(UInt32))
return(reader.ReadUInt32());
if (type==typeof(UInt64))
return(reader.ReadUInt64());
if (type==typeof(Decimal))
return(reader.ReadDecimal());
if (type==typeof(DateTime))
return(new DateTime(reader.ReadInt64()));
return ReadNonPredefinedValue(type);
}
// TODO: Add security checks
object ReadNonPredefinedValue (Type exp_type)
{
object obj=formatter.Deserialize(reader.BaseStream);
if(obj.GetType() != exp_type) {
/* We got a bogus
* object. This
* exception is the
* best match I could
* find. (.net seems
* to throw
* BadImageFormatException,
* which the docs
* state is used when
* file or dll images
* cant be loaded by
* the runtime.)
*/
throw new InvalidOperationException("Deserialized object is wrong type");
}
return obj;
}
private object ResourceValue(int index)
{
lock(this)
{
long pos=positions[index]+nameSectionOffset;
reader.BaseStream.Seek(pos, SeekOrigin.Begin);
/* Read a 7-bit encoded byte length field */
long len=Read7BitEncodedInt();
/* ... and skip that data to the info
* we want, the offset into the data
* section
*/
reader.BaseStream.Seek(len, SeekOrigin.Current);
long data_offset=reader.ReadInt32();
reader.BaseStream.Seek(data_offset+dataSectionOffset, SeekOrigin.Begin);
int type_index=Read7BitEncodedInt();
#if NET_2_0
if (resource_ver == 2)
return ReadValueVer2 (type_index);
#endif
if (type_index == -1)
return null;
return ReadValueVer1 (types[type_index]);
}
}
public void Close ()
{
Dispose(true);
}
public IDictionaryEnumerator GetEnumerator () {
if (reader == null){
throw new InvalidOperationException("ResourceReader is closed.");
}
else {
return new ResourceEnumerator (this);
}
}
IEnumerator IEnumerable.GetEnumerator ()
{
return ((IResourceReader) this).GetEnumerator();
}
void IDisposable.Dispose ()
{
Dispose(true);
}
private void Dispose (bool disposing)
{
if(disposing) {
if(reader!=null) {
reader.Close();
}
}
reader=null;
hashes=null;
positions=null;
types=null;
}
internal class ResourceEnumerator : IDictionaryEnumerator
{
private ResourceReader reader;
private int index = -1;
private bool finished = false;
internal ResourceEnumerator(ResourceReader readerToEnumerate){
reader = readerToEnumerate;
}
public virtual DictionaryEntry Entry
{
get {
if (reader.reader == null)
throw new InvalidOperationException("ResourceReader is closed.");
if (index < 0)
throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
DictionaryEntry entry = new DictionaryEntry();
entry.Key = Key;
entry.Value = Value;
return entry;
}
}
public virtual object Key
{
get {
if (reader.reader == null)
throw new InvalidOperationException("ResourceReader is closed.");
if (index < 0)
throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
return (reader.ResourceName(index));
}
}
public virtual object Value
{
get {
if (reader.reader == null)
throw new InvalidOperationException("ResourceReader is closed.");
if (index < 0)
throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
return(reader.ResourceValue(index));
}
}
public virtual object Current
{
get {
/* Entry does the checking, no
* need to repeat it here
*/
return Entry;
}
}
public virtual bool MoveNext ()
{
if (reader.reader == null)
throw new InvalidOperationException("ResourceReader is closed.");
if (finished) {
return false;
}
if (++index < reader.resourceCount){
return true;
}
else {
finished=true;
return false;
}
}
public void Reset () {
if (reader.reader == null)
throw new InvalidOperationException("ResourceReader is closed.");
index = -1;
finished = false;
}
} // internal class ResourceEnumerator
} // public sealed class ResourceReader
} // namespace System.Resources