2 // System.Resources.ResourceReader.cs
5 // Duncan Mak <duncan@ximian.com>
6 // Nick Drochak <ndrochak@gol.com>
7 // Dick Porter <dick@ximian.com>
8 // Marek Safar <marek.safar@seznam.cz>
9 // Atsushi Enomoto <atsushi@ximian.com>
10 // Larry Ewing <lewing@novell.com>
12 // (C) 2001, 2002 Ximian Inc, http://www.ximian.com
13 // Copyright (C) 2004-2005,2007-2008 Novell, Inc (http://www.novell.com)
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35 using System
.Collections
;
36 using System
.Resources
;
39 using System
.Runtime
.InteropServices
;
40 using System
.Runtime
.Serialization
;
41 using System
.Runtime
.Serialization
.Formatters
.Binary
;
42 using System
.Security
.Permissions
;
43 using System
.Collections
.Generic
;
45 namespace System
.Resources
47 internal enum PredefinedResourceType
71 [System
.Runtime
.InteropServices
.ComVisible (true)]
72 public sealed class ResourceReader
: IResourceReader
, IEnumerable
, IDisposable
76 public readonly long ValuePosition
;
77 public readonly string ResourceName
;
78 public readonly int TypeIndex
;
80 public ResourceInfo (string resourceName
, long valuePosition
, int type_index
)
82 ValuePosition
= valuePosition
;
83 ResourceName
= resourceName
;
84 TypeIndex
= type_index
;
88 struct ResourceCacheItem
90 public readonly string ResourceName
;
91 public readonly object ResourceValue
;
93 public ResourceCacheItem (string name
, object value)
96 ResourceValue
= value;
101 object readerLock
= new object ();
102 IFormatter formatter
;
103 internal int resourceCount
= 0;
107 ResourceInfo
[] infos
;
108 int dataSectionOffset
;
109 long nameSectionOffset
;
111 ResourceCacheItem
[] cache
;
112 object cache_lock
= new object ();
115 [SecurityPermission (SecurityAction
.LinkDemand
, SerializationFormatter
= true)]
116 public ResourceReader (Stream stream
)
119 throw new ArgumentNullException ("stream");
122 throw new ArgumentException ("Stream was not readable.");
124 reader
= new BinaryReader(stream
, Encoding
.UTF8
);
125 formatter
= new BinaryFormatter(null, new StreamingContext(StreamingContextStates
.File
|StreamingContextStates
.Persistence
));
130 public ResourceReader (string fileName
)
132 reader
= new BinaryReader (new FileStream(fileName
, FileMode
.Open
, FileAccess
.Read
, FileShare
.Read
));
133 formatter
= new BinaryFormatter(null, new StreamingContext(StreamingContextStates
.File
|StreamingContextStates
.Persistence
));
138 /* Read the ResourceManager header and the
139 * ResourceReader header.
141 private void ReadHeaders()
144 int manager_magic
= reader
.ReadInt32();
146 if(manager_magic
!= ResourceManager
.MagicNumber
)
147 throw new ArgumentException(String
.Format ("Stream is not a valid .resources file, magic=0x{0:x}", manager_magic
));
149 int manager_ver
= reader
.ReadInt32();
150 int manager_len
= reader
.ReadInt32();
152 /* We know how long the header is, even if
153 * the version number is too new
155 if(manager_ver
> ResourceManager
.HeaderVersionNumber
) {
156 reader
.BaseStream
.Seek(manager_len
, SeekOrigin
.Current
);
158 string reader_class
=reader
.ReadString();
159 if(!reader_class
.StartsWith("System.Resources.ResourceReader")) {
160 throw new NotSupportedException("This .resources file requires reader class " + reader_class
);
163 string set_class
=reader
.ReadString();
164 if(!set_class
.StartsWith(typeof(ResourceSet
).FullName
) && !set_class
.StartsWith("System.Resources.RuntimeResourceSet")) {
165 throw new NotSupportedException("This .resources file requires set class " + set_class
);
169 /* Now read the ResourceReader header */
170 resource_ver
= reader
.ReadInt32();
172 if(resource_ver
!= 1 && resource_ver
!= 2) {
173 throw new NotSupportedException("This .resources file requires unsupported set class version: " + resource_ver
.ToString());
176 resourceCount
= reader
.ReadInt32();
177 typeCount
= reader
.ReadInt32();
179 typeNames
=new string[typeCount
];
181 for(int i
=0; i
<typeCount
; i
++) {
182 typeNames
[i
]=reader
.ReadString();
185 /* There are between 0 and 7 bytes of
186 * padding here, consisting of the
187 * letters PAD. The next item (Hash
188 * values for each resource name) need
189 * to be aligned on an 8-byte
193 int pad_align
=(int)(reader
.BaseStream
.Position
& 7);
197 pad_chars
=8-pad_align
;
200 for(int i
=0; i
<pad_chars
; i
++) {
201 byte pad_byte
=reader
.ReadByte();
202 if(pad_byte
!="PAD"[i
%3]) {
203 throw new ArgumentException("Malformed .resources file (padding values incorrect)");
206 /* Read in the hash values for each
207 * resource name. These can be used
208 * by ResourceSet (calling internal
209 * methods) to do a fast compare on
210 * resource names without doing
211 * expensive string compares (but we
212 * dont do that yet, so far we only
213 * implement the Enumerator interface)
215 hashes
=new int[resourceCount
];
216 for(int i
=0; i
<resourceCount
; i
++) {
217 hashes
[i
]=reader
.ReadInt32();
220 /* Read in the virtual offsets for
223 long[] positions
= new long [resourceCount
];
224 for(int i
= 0; i
< resourceCount
; i
++)
225 positions
[i
] = reader
.ReadInt32();
227 dataSectionOffset
= reader
.ReadInt32();
228 nameSectionOffset
= reader
.BaseStream
.Position
;
230 long origPosition
= reader
.BaseStream
.Position
;
231 infos
= new ResourceInfo
[resourceCount
];
232 for (int i
= 0; i
< resourceCount
; i
++)
233 CreateResourceInfo (positions
[i
], ref infos
[i
]);
234 reader
.BaseStream
.Seek (origPosition
, SeekOrigin
.Begin
);
237 } catch(EndOfStreamException e
) {
238 throw new ArgumentException("Stream is not a valid .resources file! It was possibly truncated.", e
);
242 void CreateResourceInfo (long position
, ref ResourceInfo info
)
244 long pos
= position
+ nameSectionOffset
;
246 reader
.BaseStream
.Seek (pos
, SeekOrigin
.Begin
);
249 int len
= Read7BitEncodedInt ();
250 byte[] str
= new byte [len
];
252 reader
.Read (str
, 0, len
);
253 string resourceName
= Encoding
.Unicode
.GetString (str
);
255 long data_offset
= reader
.ReadInt32 () + dataSectionOffset
;
256 reader
.BaseStream
.Seek (data_offset
, SeekOrigin
.Begin
);
257 int type_index
= Read7BitEncodedInt ();
259 info
= new ResourceInfo (resourceName
, reader
.BaseStream
.Position
, type_index
);
262 /* Cut and pasted from BinaryReader, because it's
265 private int Read7BitEncodedInt() {
271 b
= reader
.ReadByte();
273 ret
= ret
| ((b
& 0x7f) << shift
);
275 } while ((b
& 0x80) == 0x80);
280 object ReadValueVer2 (int type_index
)
282 switch ((PredefinedResourceType
)type_index
)
284 case PredefinedResourceType
.Null
:
287 case PredefinedResourceType
.String
:
288 return reader
.ReadString();
290 case PredefinedResourceType
.Bool
:
291 return reader
.ReadBoolean ();
293 case PredefinedResourceType
.Char
:
294 return (char)reader
.ReadUInt16();
296 case PredefinedResourceType
.Byte
:
297 return reader
.ReadByte();
299 case PredefinedResourceType
.SByte
:
300 return reader
.ReadSByte();
302 case PredefinedResourceType
.Int16
:
303 return reader
.ReadInt16();
305 case PredefinedResourceType
.UInt16
:
306 return reader
.ReadUInt16();
308 case PredefinedResourceType
.Int32
:
309 return reader
.ReadInt32();
311 case PredefinedResourceType
.UInt32
:
312 return reader
.ReadUInt32();
314 case PredefinedResourceType
.Int64
:
315 return reader
.ReadInt64();
317 case PredefinedResourceType
.UInt64
:
318 return reader
.ReadUInt64();
320 case PredefinedResourceType
.Single
:
321 return reader
.ReadSingle();
323 case PredefinedResourceType
.Double
:
324 return reader
.ReadDouble();
326 case PredefinedResourceType
.Decimal
:
327 return reader
.ReadDecimal();
329 case PredefinedResourceType
.DateTime
:
330 return new DateTime(reader
.ReadInt64());
332 case PredefinedResourceType
.TimeSpan
:
333 return new TimeSpan(reader
.ReadInt64());
335 case PredefinedResourceType
.ByteArray
:
336 return reader
.ReadBytes (reader
.ReadInt32 ());
338 case PredefinedResourceType
.Stream
:
339 // FIXME: create pinned UnmanagedMemoryStream for efficiency.
340 byte [] bytes
= new byte [reader
.ReadUInt32 ()];
341 reader
.Read (bytes
, 0, bytes
.Length
);
342 return new MemoryStream (bytes
);
345 type_index
-= (int)PredefinedResourceType
.FistCustom
;
346 return ReadNonPredefinedValue (Type
.GetType (typeNames
[type_index
], true));
349 object ReadValueVer1 (Type type
)
351 // The most common first
352 if (type
==typeof(String
))
353 return reader
.ReadString();
354 if (type
==typeof(Int32
))
355 return reader
.ReadInt32();
356 if (type
==typeof(Byte
))
357 return(reader
.ReadByte());
358 if (type
==typeof(Double
))
359 return(reader
.ReadDouble());
360 if (type
==typeof(Int16
))
361 return(reader
.ReadInt16());
362 if (type
==typeof(Int64
))
363 return(reader
.ReadInt64());
364 if (type
==typeof(SByte
))
365 return(reader
.ReadSByte());
366 if (type
==typeof(Single
))
367 return(reader
.ReadSingle());
368 if (type
==typeof(TimeSpan
))
369 return(new TimeSpan(reader
.ReadInt64()));
370 if (type
==typeof(UInt16
))
371 return(reader
.ReadUInt16());
372 if (type
==typeof(UInt32
))
373 return(reader
.ReadUInt32());
374 if (type
==typeof(UInt64
))
375 return(reader
.ReadUInt64());
376 if (type
==typeof(Decimal
))
377 return(reader
.ReadDecimal());
378 if (type
==typeof(DateTime
))
379 return(new DateTime(reader
.ReadInt64()));
381 return ReadNonPredefinedValue(type
);
384 // TODO: Add security checks
385 object ReadNonPredefinedValue (Type exp_type
)
387 object obj
=formatter
.Deserialize(reader
.BaseStream
);
388 if(obj
.GetType() != exp_type
) {
395 * BadImageFormatException,
402 throw new InvalidOperationException("Deserialized object is wrong type");
407 void LoadResourceValues (ResourceCacheItem
[] store
)
413 for (int i
= 0; i
< resourceCount
; i
++) {
415 if (ri
.TypeIndex
== -1) {
416 store
[i
] = new ResourceCacheItem (ri
.ResourceName
, null);
420 reader
.BaseStream
.Seek (ri
.ValuePosition
, SeekOrigin
.Begin
);
421 if (resource_ver
== 2)
422 value = ReadValueVer2 (ri
.TypeIndex
);
424 value = ReadValueVer1 (Type
.GetType (typeNames
[ri
.TypeIndex
], true));
426 store
[i
] = new ResourceCacheItem (ri
.ResourceName
, value);
431 internal UnmanagedMemoryStream
ResourceValueAsStream (string name
, int index
)
433 ResourceInfo ri
= infos
[index
];
434 if ((PredefinedResourceType
)ri
.TypeIndex
!= PredefinedResourceType
.Stream
)
435 throw new InvalidOperationException (String
.Format ("Resource '{0}' was not a Stream. Use GetObject() instead.", name
));
438 reader
.BaseStream
.Seek (ri
.ValuePosition
, SeekOrigin
.Begin
);
440 // here we return a Stream from exactly
441 // current position so that the returned
442 // Stream represents a single object stream.
443 long slen
= reader
.ReadInt32();
444 UnmanagedMemoryStream basePtrStream
= reader
.BaseStream
as UnmanagedMemoryStream
;
446 if (basePtrStream
!= null) {
447 return new UnmanagedMemoryStream (basePtrStream
.PositionPointer
, slen
);
449 IntPtr ptr
= Marshal
.AllocHGlobal ((int) slen
);
450 byte* addr
= (byte*) ptr
.ToPointer ();
451 UnmanagedMemoryStream ms
= new UnmanagedMemoryStream (addr
, slen
, slen
, FileAccess
.ReadWrite
);
452 // The memory resource must be freed
453 // when the stream is disposed.
454 ms
.Closed
+= delegate (object o
, EventArgs e
) {
455 Marshal
.FreeHGlobal (ptr
);
458 byte [] bytes
= new byte [slen
< 1024 ? slen
: 1024];
460 int x
= reader
.Read (bytes
, 0, (int)Math
.Min (bytes
.Length
, slen
));
463 throw new FormatException ("The resource data is corrupt. Resource stream ended");
465 ms
.Write (bytes
, 0, x
);
468 ms
.Seek (0, SeekOrigin
.Begin
);
480 public IDictionaryEnumerator
GetEnumerator () {
482 throw new InvalidOperationException("ResourceReader is closed.");
485 return new ResourceEnumerator (this);
489 IEnumerator IEnumerable
.GetEnumerator ()
491 return ((IResourceReader
) this).GetEnumerator();
494 public void GetResourceData (string resourceName
, out string resourceType
, out byte [] resourceData
)
496 if (resourceName
== null)
497 throw new ArgumentNullException ("resourceName");
498 ResourceEnumerator en
= new ResourceEnumerator (this);
499 while (en
.MoveNext ())
500 if ((string) en
.Key
== resourceName
) {
501 GetResourceDataAt (en
.Index
, out resourceType
, out resourceData
);
504 throw new ArgumentException (String
.Format ("Specified resource not found: {0}", resourceName
));
507 private void GetResourceDataAt (int index
, out string resourceType
, out byte [] data
)
509 ResourceInfo ri
= infos
[index
];
510 int type_index
= ri
.TypeIndex
;
511 if (type_index
== -1)
512 throw new FormatException ("The resource data is corrupt");
515 reader
.BaseStream
.Seek (ri
.ValuePosition
, SeekOrigin
.Begin
);
516 long pos2
= reader
.BaseStream
.Position
;
518 // Simply read data, and seek back to the original position
519 if (resource_ver
== 2) {
520 if (type_index
>= (int) PredefinedResourceType
.FistCustom
) {
521 int typenameidx
= type_index
- (int)PredefinedResourceType
.FistCustom
;
522 if (typenameidx
>= typeNames
.Length
)
523 throw new FormatException ("The resource data is corrupt. Invalid index to types");
524 resourceType
= typeNames
[typenameidx
];
527 resourceType
= "ResourceTypeCode." + (PredefinedResourceType
) type_index
;
528 ReadValueVer2 (type_index
);
530 // resource ver 1 == untyped
531 resourceType
= "ResourceTypeCode.Null";
532 ReadValueVer1 (Type
.GetType (typeNames
[type_index
], true));
535 // FIXME: the data size is wrong.
536 int datalen
= (int) (reader
.BaseStream
.Position
- pos2
);
537 reader
.BaseStream
.Seek (-datalen
, SeekOrigin
.Current
);
538 data
= new byte [datalen
];
539 reader
.BaseStream
.Read (data
, 0, datalen
);
543 void IDisposable
.Dispose ()
548 private void Dispose (bool disposing
)
563 internal sealed class ResourceEnumerator
: IDictionaryEnumerator
565 private ResourceReader reader
;
566 private int index
= -1;
567 private bool finished
;
569 internal ResourceEnumerator(ResourceReader readerToEnumerate
)
571 reader
= readerToEnumerate
;
577 get { return index; }
580 public DictionaryEntry Entry
{
582 if (reader
.reader
== null)
583 throw new InvalidOperationException("ResourceReader is closed.");
585 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
587 return new DictionaryEntry(Key
, Value
);
594 if (reader
.reader
== null)
595 throw new InvalidOperationException("ResourceReader is closed.");
597 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
599 return reader
.cache
[index
].ResourceName
;
606 if (reader
.reader
== null)
607 throw new InvalidOperationException("ResourceReader is closed.");
609 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
610 return reader
.cache
[index
].ResourceValue
;
614 public UnmanagedMemoryStream ValueAsStream
617 if (reader
.reader
== null)
618 throw new InvalidOperationException("ResourceReader is closed.");
620 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
621 return(reader
.ResourceValueAsStream((string) Key
, index
));
625 public object Current
628 /* Entry does the checking, no
629 * need to repeat it here
635 public bool MoveNext ()
637 if (reader
.reader
== null)
638 throw new InvalidOperationException("ResourceReader is closed.");
643 if (++index
< reader
.resourceCount
){
651 public void Reset () {
652 if (reader
.reader
== null)
653 throw new InvalidOperationException("ResourceReader is closed.");
660 if (reader
.cache
!= null)
663 lock (reader
.cache_lock
) {
664 if (reader
.cache
!= null)
667 ResourceCacheItem
[] resources
= new ResourceCacheItem
[reader
.resourceCount
];
668 reader
.LoadResourceValues (resources
);
669 reader
.cache
= resources
;
672 } // internal class ResourceEnumerator
673 } // public sealed class ResourceReader
674 } // namespace System.Resources