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;
100 unsafe class HGlobalUnmanagedMemoryStream : UnmanagedMemoryStream
104 public HGlobalUnmanagedMemoryStream (byte* pointer, long length, IntPtr ptr)
105 : base (pointer, length, length, FileAccess.ReadWrite)
110 protected override void Dispose (bool disposing)
113 Marshal.FreeHGlobal (ptr);
116 base.Dispose (disposing);
121 object readerLock = new object ();
122 IFormatter formatter;
123 internal int resourceCount = 0;
127 ResourceInfo[] infos;
128 int dataSectionOffset;
129 long nameSectionOffset;
131 ResourceCacheItem[] cache;
132 object cache_lock = new object ();
135 [SecurityPermission (SecurityAction.LinkDemand, SerializationFormatter = true)]
136 public ResourceReader (Stream stream)
139 throw new ArgumentNullException ("stream");
142 throw new ArgumentException ("Stream was not readable.");
144 reader = new BinaryReader(stream, Encoding.UTF8);
145 formatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File|StreamingContextStates.Persistence));
150 public ResourceReader (string fileName)
152 reader = new BinaryReader (new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read));
153 formatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File|StreamingContextStates.Persistence));
158 /* Read the ResourceManager header and the
159 * ResourceReader header.
161 private void ReadHeaders()
164 int manager_magic = reader.ReadInt32();
166 if(manager_magic != ResourceManager.MagicNumber)
167 throw new ArgumentException(String.Format ("Stream is not a valid .resources file, magic=0x{0:x}", manager_magic));
169 int manager_ver = reader.ReadInt32();
170 int manager_len = reader.ReadInt32();
172 /* We know how long the header is, even if
173 * the version number is too new
175 if(manager_ver > ResourceManager.HeaderVersionNumber) {
176 reader.BaseStream.Seek(manager_len, SeekOrigin.Current);
178 string reader_class=reader.ReadString();
179 if(!reader_class.StartsWith("System.Resources.ResourceReader")) {
180 throw new NotSupportedException("This .resources file requires reader class " + reader_class);
183 string set_class=reader.ReadString();
184 if(!set_class.StartsWith(typeof(ResourceSet).FullName) && !set_class.StartsWith("System.Resources.RuntimeResourceSet")) {
185 throw new NotSupportedException("This .resources file requires set class " + set_class);
189 /* Now read the ResourceReader header */
190 resource_ver = reader.ReadInt32();
192 if(resource_ver != 1 && resource_ver != 2) {
193 throw new NotSupportedException("This .resources file requires unsupported set class version: " + resource_ver.ToString());
196 resourceCount = reader.ReadInt32();
197 typeCount = reader.ReadInt32();
199 typeNames=new string[typeCount];
201 for(int i=0; i<typeCount; i++) {
202 typeNames[i]=reader.ReadString();
205 /* There are between 0 and 7 bytes of
206 * padding here, consisting of the
207 * letters PAD. The next item (Hash
208 * values for each resource name) need
209 * to be aligned on an 8-byte
213 int pad_align=(int)(reader.BaseStream.Position & 7);
217 pad_chars=8-pad_align;
220 for(int i=0; i<pad_chars; i++) {
221 byte pad_byte=reader.ReadByte();
222 if(pad_byte!="PAD"[i%3]) {
223 throw new ArgumentException("Malformed .resources file (padding values incorrect)");
226 /* Read in the hash values for each
227 * resource name. These can be used
228 * by ResourceSet (calling internal
229 * methods) to do a fast compare on
230 * resource names without doing
231 * expensive string compares (but we
232 * dont do that yet, so far we only
233 * implement the Enumerator interface)
235 hashes=new int[resourceCount];
236 for(int i=0; i<resourceCount; i++) {
237 hashes[i]=reader.ReadInt32();
240 /* Read in the virtual offsets for
243 long[] positions = new long [resourceCount];
244 for(int i = 0; i < resourceCount; i++)
245 positions [i] = reader.ReadInt32();
247 dataSectionOffset = reader.ReadInt32();
248 nameSectionOffset = reader.BaseStream.Position;
250 long origPosition = reader.BaseStream.Position;
251 infos = new ResourceInfo [resourceCount];
252 for (int i = 0; i < resourceCount; i++)
253 CreateResourceInfo (positions [i], ref infos [i]);
254 reader.BaseStream.Seek (origPosition, SeekOrigin.Begin);
257 } catch(EndOfStreamException e) {
258 throw new ArgumentException("Stream is not a valid .resources file! It was possibly truncated.", e);
262 void CreateResourceInfo (long position, ref ResourceInfo info)
264 long pos = position + nameSectionOffset;
266 reader.BaseStream.Seek (pos, SeekOrigin.Begin);
269 int len = Read7BitEncodedInt ();
270 byte[] str = new byte [len];
272 reader.Read (str, 0, len);
273 string resourceName = Encoding.Unicode.GetString (str);
275 long data_offset = reader.ReadInt32 () + dataSectionOffset;
276 reader.BaseStream.Seek (data_offset, SeekOrigin.Begin);
277 int type_index = Read7BitEncodedInt ();
279 info = new ResourceInfo (resourceName, reader.BaseStream.Position, type_index);
282 /* Cut and pasted from BinaryReader, because it's
285 private int Read7BitEncodedInt() {
291 b = reader.ReadByte();
293 ret = ret | ((b & 0x7f) << shift);
295 } while ((b & 0x80) == 0x80);
300 object ReadValueVer2 (int type_index)
302 switch ((PredefinedResourceType)type_index)
304 case PredefinedResourceType.Null:
307 case PredefinedResourceType.String:
308 return reader.ReadString();
310 case PredefinedResourceType.Bool:
311 return reader.ReadBoolean ();
313 case PredefinedResourceType.Char:
314 return (char)reader.ReadUInt16();
316 case PredefinedResourceType.Byte:
317 return reader.ReadByte();
319 case PredefinedResourceType.SByte:
320 return reader.ReadSByte();
322 case PredefinedResourceType.Int16:
323 return reader.ReadInt16();
325 case PredefinedResourceType.UInt16:
326 return reader.ReadUInt16();
328 case PredefinedResourceType.Int32:
329 return reader.ReadInt32();
331 case PredefinedResourceType.UInt32:
332 return reader.ReadUInt32();
334 case PredefinedResourceType.Int64:
335 return reader.ReadInt64();
337 case PredefinedResourceType.UInt64:
338 return reader.ReadUInt64();
340 case PredefinedResourceType.Single:
341 return reader.ReadSingle();
343 case PredefinedResourceType.Double:
344 return reader.ReadDouble();
346 case PredefinedResourceType.Decimal:
347 return reader.ReadDecimal();
349 case PredefinedResourceType.DateTime:
350 return new DateTime(reader.ReadInt64());
352 case PredefinedResourceType.TimeSpan:
353 return new TimeSpan(reader.ReadInt64());
355 case PredefinedResourceType.ByteArray:
356 return reader.ReadBytes (reader.ReadInt32 ());
358 case PredefinedResourceType.Stream:
359 // FIXME: create pinned UnmanagedMemoryStream for efficiency.
360 byte [] bytes = new byte [reader.ReadUInt32 ()];
361 reader.Read (bytes, 0, bytes.Length);
362 return new MemoryStream (bytes);
365 type_index -= (int)PredefinedResourceType.FistCustom;
366 return ReadNonPredefinedValue (Type.GetType (typeNames[type_index], true));
369 object ReadValueVer1 (Type type)
371 // The most common first
372 if (type==typeof(String))
373 return reader.ReadString();
374 if (type==typeof(Int32))
375 return reader.ReadInt32();
376 if (type==typeof(Byte))
377 return(reader.ReadByte());
378 if (type==typeof(Double))
379 return(reader.ReadDouble());
380 if (type==typeof(Int16))
381 return(reader.ReadInt16());
382 if (type==typeof(Int64))
383 return(reader.ReadInt64());
384 if (type==typeof(SByte))
385 return(reader.ReadSByte());
386 if (type==typeof(Single))
387 return(reader.ReadSingle());
388 if (type==typeof(TimeSpan))
389 return(new TimeSpan(reader.ReadInt64()));
390 if (type==typeof(UInt16))
391 return(reader.ReadUInt16());
392 if (type==typeof(UInt32))
393 return(reader.ReadUInt32());
394 if (type==typeof(UInt64))
395 return(reader.ReadUInt64());
396 if (type==typeof(Decimal))
397 return(reader.ReadDecimal());
398 if (type==typeof(DateTime))
399 return(new DateTime(reader.ReadInt64()));
401 return ReadNonPredefinedValue(type);
404 // TODO: Add security checks
405 object ReadNonPredefinedValue (Type exp_type)
407 object obj=formatter.Deserialize(reader.BaseStream);
408 if(obj.GetType() != exp_type) {
415 * BadImageFormatException,
422 throw new InvalidOperationException("Deserialized object is wrong type");
427 void LoadResourceValues (ResourceCacheItem[] store)
433 for (int i = 0; i < resourceCount; i++) {
435 if (ri.TypeIndex == -1) {
436 store [i] = new ResourceCacheItem (ri.ResourceName, null);
440 reader.BaseStream.Seek (ri.ValuePosition, SeekOrigin.Begin);
441 if (resource_ver == 2)
442 value = ReadValueVer2 (ri.TypeIndex);
444 value = ReadValueVer1 (Type.GetType (typeNames [ri.TypeIndex], true));
446 store [i] = new ResourceCacheItem (ri.ResourceName, value);
451 internal UnmanagedMemoryStream ResourceValueAsStream (string name, int index)
453 ResourceInfo ri = infos [index];
454 if ((PredefinedResourceType)ri.TypeIndex != PredefinedResourceType.Stream)
455 throw new InvalidOperationException (String.Format ("Resource '{0}' was not a Stream. Use GetObject() instead.", name));
458 reader.BaseStream.Seek (ri.ValuePosition, SeekOrigin.Begin);
460 // here we return a Stream from exactly
461 // current position so that the returned
462 // Stream represents a single object stream.
463 long slen = reader.ReadInt32();
464 UnmanagedMemoryStream basePtrStream = reader.BaseStream as UnmanagedMemoryStream;
466 if (basePtrStream != null) {
467 return new UnmanagedMemoryStream (basePtrStream.PositionPointer, slen);
469 IntPtr ptr = Marshal.AllocHGlobal ((int) slen);
470 byte* addr = (byte*) ptr.ToPointer ();
471 UnmanagedMemoryStream ms = new HGlobalUnmanagedMemoryStream (addr, slen, ptr);
473 byte [] bytes = new byte [slen < 1024 ? slen : 1024];
475 int x = reader.Read (bytes, 0, (int)Math.Min (bytes.Length, slen));
478 throw new FormatException ("The resource data is corrupt. Resource stream ended");
480 ms.Write (bytes, 0, x);
483 ms.Seek (0, SeekOrigin.Begin);
495 public void Dispose ()
500 public IDictionaryEnumerator GetEnumerator () {
502 throw new InvalidOperationException("ResourceReader is closed.");
505 return new ResourceEnumerator (this);
509 IEnumerator IEnumerable.GetEnumerator ()
511 return ((IResourceReader) this).GetEnumerator();
514 public void GetResourceData (string resourceName, out string resourceType, out byte [] resourceData)
516 if (resourceName == null)
517 throw new ArgumentNullException ("resourceName");
518 ResourceEnumerator en = new ResourceEnumerator (this);
519 while (en.MoveNext ())
520 if ((string) en.Key == resourceName) {
521 GetResourceDataAt (en.Index, out resourceType, out resourceData);
524 throw new ArgumentException (String.Format ("Specified resource not found: {0}", resourceName));
527 private void GetResourceDataAt (int index, out string resourceType, out byte [] data)
529 ResourceInfo ri = infos [index];
530 int type_index = ri.TypeIndex;
531 if (type_index == -1)
532 throw new FormatException ("The resource data is corrupt");
535 reader.BaseStream.Seek (ri.ValuePosition, SeekOrigin.Begin);
536 long pos2 = reader.BaseStream.Position;
538 // Simply read data, and seek back to the original position
539 if (resource_ver == 2) {
540 if (type_index >= (int) PredefinedResourceType.FistCustom) {
541 int typenameidx = type_index - (int)PredefinedResourceType.FistCustom;
542 if (typenameidx >= typeNames.Length)
543 throw new FormatException ("The resource data is corrupt. Invalid index to types");
544 resourceType = typeNames[typenameidx];
547 resourceType = "ResourceTypeCode." + (PredefinedResourceType) type_index;
548 ReadValueVer2 (type_index);
550 // resource ver 1 == untyped
551 resourceType = "ResourceTypeCode.Null";
552 ReadValueVer1 (Type.GetType (typeNames [type_index], true));
555 // FIXME: the data size is wrong.
556 int datalen = (int) (reader.BaseStream.Position - pos2);
557 reader.BaseStream.Seek (-datalen, SeekOrigin.Current);
558 data = new byte [datalen];
559 reader.BaseStream.Read (data, 0, datalen);
563 private void Dispose (bool disposing)
578 internal sealed class ResourceEnumerator : IDictionaryEnumerator
580 private ResourceReader reader;
581 private int index = -1;
582 private bool finished;
584 internal ResourceEnumerator(ResourceReader readerToEnumerate)
586 reader = readerToEnumerate;
592 get { return index; }
595 public DictionaryEntry Entry {
597 if (reader.reader == null)
598 throw new InvalidOperationException("ResourceReader is closed.");
600 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
602 return new DictionaryEntry(Key, Value);
609 if (reader.reader == null)
610 throw new InvalidOperationException("ResourceReader is closed.");
612 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
614 return reader.cache [index].ResourceName;
621 if (reader.reader == null)
622 throw new InvalidOperationException("ResourceReader is closed.");
624 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
625 return reader.cache [index].ResourceValue;
629 public UnmanagedMemoryStream ValueAsStream
632 if (reader.reader == null)
633 throw new InvalidOperationException("ResourceReader is closed.");
635 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
636 return(reader.ResourceValueAsStream((string) Key, index));
640 public object Current
643 /* Entry does the checking, no
644 * need to repeat it here
650 public bool MoveNext ()
652 if (reader.reader == null)
653 throw new InvalidOperationException("ResourceReader is closed.");
658 if (++index < reader.resourceCount){
666 public void Reset () {
667 if (reader.reader == null)
668 throw new InvalidOperationException("ResourceReader is closed.");
675 if (reader.cache != null)
678 lock (reader.cache_lock) {
679 if (reader.cache != null)
682 ResourceCacheItem[] resources = new ResourceCacheItem [reader.resourceCount];
683 reader.LoadResourceValues (resources);
684 reader.cache = resources;
687 } // internal class ResourceEnumerator
688 } // public sealed class ResourceReader
689 } // namespace System.Resources