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>
11 // (C) 2001, 2002 Ximian Inc, http://www.ximian.com
12 // Copyright (C) 2004-2005,2007 Novell, Inc (http://www.novell.com)
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 using System.Collections;
35 using System.Resources;
38 using System.Runtime.InteropServices;
39 using System.Runtime.Serialization;
40 using System.Runtime.Serialization.Formatters.Binary;
41 using System.Security.Permissions;
43 namespace System.Resources
45 internal enum PredefinedResourceType
70 [System.Runtime.InteropServices.ComVisible (true)]
72 public sealed class ResourceReader : IResourceReader, IEnumerable, IDisposable
76 internal int resourceCount = 0;
81 int dataSectionOffset;
82 long nameSectionOffset;
86 [SecurityPermission (SecurityAction.LinkDemand, SerializationFormatter = true)]
87 public ResourceReader (Stream stream)
90 throw new ArgumentNullException ("Value cannot be null.");
93 throw new ArgumentException ("Stream was not readable.");
95 reader = new BinaryReader(stream, Encoding.UTF8);
96 formatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File|StreamingContextStates.Persistence));
101 public ResourceReader (string fileName)
103 if (fileName == null)
104 throw new ArgumentNullException ("Path cannot be null.");
106 if (!System.IO.File.Exists (fileName))
107 throw new FileNotFoundException ("Could not find file " + Path.GetFullPath(fileName));
109 reader = new BinaryReader (new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read));
110 formatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File|StreamingContextStates.Persistence));
115 /* Read the ResourceManager header and the
116 * ResourceReader header.
118 private void ReadHeaders()
121 int manager_magic = reader.ReadInt32();
123 if(manager_magic != ResourceManager.MagicNumber)
124 throw new ArgumentException(String.Format ("Stream is not a valid .resources file, magic=0x{0:x}", manager_magic));
126 int manager_ver = reader.ReadInt32();
127 int manager_len = reader.ReadInt32();
129 /* We know how long the header is, even if
130 * the version number is too new
132 if(manager_ver > ResourceManager.HeaderVersionNumber) {
133 reader.BaseStream.Seek(manager_len, SeekOrigin.Current);
135 string reader_class=reader.ReadString();
136 if(!reader_class.StartsWith("System.Resources.ResourceReader")) {
137 throw new NotSupportedException("This .resources file requires reader class " + reader_class);
140 string set_class=reader.ReadString();
141 if(!set_class.StartsWith(typeof(ResourceSet).FullName) && !set_class.StartsWith("System.Resources.RuntimeResourceSet")) {
142 throw new NotSupportedException("This .resources file requires set class " + set_class);
146 /* Now read the ResourceReader header */
147 resource_ver = reader.ReadInt32();
154 throw new NotSupportedException("This .resources file requires unsupported set class version: " + resource_ver.ToString());
157 resourceCount = reader.ReadInt32();
158 typeCount = reader.ReadInt32();
160 types=new Type[typeCount];
162 for(int i=0; i<typeCount; i++) {
163 types[i]=Type.GetType(reader.ReadString(), true);
166 /* There are between 0 and 7 bytes of
167 * padding here, consisting of the
168 * letters PAD. The next item (Hash
169 * values for each resource name) need
170 * to be aligned on an 8-byte
174 int pad_align=(int)(reader.BaseStream.Position & 7);
178 pad_chars=8-pad_align;
181 for(int i=0; i<pad_chars; i++) {
182 byte pad_byte=reader.ReadByte();
183 if(pad_byte!="PAD"[i%3]) {
184 throw new ArgumentException("Malformed .resources file (padding values incorrect)");
187 /* Read in the hash values for each
188 * resource name. These can be used
189 * by ResourceSet (calling internal
190 * methods) to do a fast compare on
191 * resource names without doing
192 * expensive string compares (but we
193 * dont do that yet, so far we only
194 * implement the Enumerator interface)
196 hashes=new int[resourceCount];
197 for(int i=0; i<resourceCount; i++) {
198 hashes[i]=reader.ReadInt32();
201 /* Read in the virtual offsets for
204 positions=new long[resourceCount];
205 for(int i=0; i<resourceCount; i++) {
206 positions[i]=reader.ReadInt32();
209 dataSectionOffset = reader.ReadInt32();
210 nameSectionOffset = reader.BaseStream.Position;
211 } catch(EndOfStreamException e) {
212 throw new ArgumentException("Stream is not a valid .resources file! It was possibly truncated.", e);
216 /* Cut and pasted from BinaryReader, because it's
219 private int Read7BitEncodedInt() {
225 b = reader.ReadByte();
227 ret = ret | ((b & 0x7f) << shift);
229 } while ((b & 0x80) == 0x80);
234 private string ResourceName(int index)
238 long pos=positions[index]+nameSectionOffset;
239 reader.BaseStream.Seek(pos, SeekOrigin.Begin);
241 /* Read a 7-bit encoded byte length field */
242 int len=Read7BitEncodedInt();
243 byte[] str=new byte[len];
245 reader.Read(str, 0, len);
246 return Encoding.Unicode.GetString(str);
250 // TODO: Read complex types
251 object ReadValueVer2 (int type_index)
253 switch ((PredefinedResourceType)type_index)
255 case PredefinedResourceType.Null:
258 case PredefinedResourceType.String:
259 return reader.ReadString();
261 case PredefinedResourceType.Bool:
262 return reader.ReadBoolean ();
264 case PredefinedResourceType.Char:
265 return (char)reader.ReadUInt16();
267 case PredefinedResourceType.Byte:
268 return reader.ReadByte();
270 case PredefinedResourceType.SByte:
271 return reader.ReadSByte();
273 case PredefinedResourceType.Int16:
274 return reader.ReadInt16();
276 case PredefinedResourceType.UInt16:
277 return reader.ReadUInt16();
279 case PredefinedResourceType.Int32:
280 return reader.ReadInt32();
282 case PredefinedResourceType.UInt32:
283 return reader.ReadUInt32();
285 case PredefinedResourceType.Int64:
286 return reader.ReadInt64();
288 case PredefinedResourceType.UInt64:
289 return reader.ReadUInt64();
291 case PredefinedResourceType.Single:
292 return reader.ReadSingle();
294 case PredefinedResourceType.Double:
295 return reader.ReadDouble();
297 case PredefinedResourceType.Decimal:
298 return reader.ReadDecimal();
300 case PredefinedResourceType.DateTime:
301 return new DateTime(reader.ReadInt64());
303 case PredefinedResourceType.TimeSpan:
304 return new TimeSpan(reader.ReadInt64());
306 case PredefinedResourceType.ByteArray:
307 return reader.ReadBytes (reader.ReadInt32 ());
309 case PredefinedResourceType.Stream:
310 // FIXME: create pinned UnmanagedMemoryStream for efficiency.
311 byte [] bytes = new byte [reader.ReadUInt32 ()];
312 reader.Read (bytes, 0, bytes.Length);
313 return new MemoryStream (bytes);
316 type_index -= (int)PredefinedResourceType.FistCustom;
317 return ReadNonPredefinedValue (types[type_index]);
320 object ReadValueVer1 (Type type)
322 // The most common first
323 if (type==typeof(String))
324 return reader.ReadString();
325 if (type==typeof(Int32))
326 return reader.ReadInt32();
327 if (type==typeof(Byte))
328 return(reader.ReadByte());
329 if (type==typeof(Double))
330 return(reader.ReadDouble());
331 if (type==typeof(Int16))
332 return(reader.ReadInt16());
333 if (type==typeof(Int64))
334 return(reader.ReadInt64());
335 if (type==typeof(SByte))
336 return(reader.ReadSByte());
337 if (type==typeof(Single))
338 return(reader.ReadSingle());
339 if (type==typeof(TimeSpan))
340 return(new TimeSpan(reader.ReadInt64()));
341 if (type==typeof(UInt16))
342 return(reader.ReadUInt16());
343 if (type==typeof(UInt32))
344 return(reader.ReadUInt32());
345 if (type==typeof(UInt64))
346 return(reader.ReadUInt64());
347 if (type==typeof(Decimal))
348 return(reader.ReadDecimal());
349 if (type==typeof(DateTime))
350 return(new DateTime(reader.ReadInt64()));
352 return ReadNonPredefinedValue(type);
355 // TODO: Add security checks
356 object ReadNonPredefinedValue (Type exp_type)
358 object obj=formatter.Deserialize(reader.BaseStream);
359 if(obj.GetType() != exp_type) {
366 * BadImageFormatException,
373 throw new InvalidOperationException("Deserialized object is wrong type");
378 private object ResourceValue(int index)
382 long pos=positions[index]+nameSectionOffset;
383 reader.BaseStream.Seek(pos, SeekOrigin.Begin);
385 /* Read a 7-bit encoded byte length field */
386 long len=Read7BitEncodedInt();
387 /* ... and skip that data to the info
388 * we want, the offset into the data
391 reader.BaseStream.Seek(len, SeekOrigin.Current);
393 long data_offset=reader.ReadInt32();
394 reader.BaseStream.Seek(data_offset+dataSectionOffset, SeekOrigin.Begin);
395 int type_index=Read7BitEncodedInt();
397 if (type_index == -1)
400 if (resource_ver == 2)
401 return ReadValueVer2 (type_index);
404 return ReadValueVer1 (types[type_index]);
409 internal UnmanagedMemoryStream ResourceValueAsStream (string name, int index)
413 long pos=positions[index]+nameSectionOffset;
414 reader.BaseStream.Seek(pos, SeekOrigin.Begin);
416 /* Read a 7-bit encoded byte length field */
417 long len=Read7BitEncodedInt();
418 /* ... and skip that data to the info
419 * we want, the offset into the data
422 reader.BaseStream.Seek(len, SeekOrigin.Current);
424 long data_offset=reader.ReadInt32();
425 reader.BaseStream.Seek(data_offset+dataSectionOffset, SeekOrigin.Begin);
426 int type_index=Read7BitEncodedInt();
427 if ((PredefinedResourceType)type_index != PredefinedResourceType.Stream)
428 throw new InvalidOperationException (String.Format ("Resource '{0}' was not a Stream. Use GetObject() instead.", name));
430 // here we return a Stream from exactly
431 // current position so that the returned
432 // Stream represents a single object stream.
433 long slen = reader.ReadInt32();
434 IntPtrStream basePtrStream = reader.BaseStream as IntPtrStream;
436 if (basePtrStream != null) {
437 byte* addr = (byte*) basePtrStream.BaseAddress.ToPointer ();
438 addr += basePtrStream.Position;
439 return new UnmanagedMemoryStream ((byte*) (void*) addr, slen);
441 IntPtr ptr = Marshal.AllocHGlobal ((int) slen);
442 byte* addr = (byte*) ptr.ToPointer ();
443 UnmanagedMemoryStream ms = new UnmanagedMemoryStream (addr, slen, slen, FileAccess.ReadWrite);
444 // The memory resource must be freed
445 // when the stream is disposed.
446 ms.Closed += delegate (object o, EventArgs e) {
447 Marshal.FreeHGlobal (ptr);
449 byte [] bytes = new byte [slen < 1024 ? slen : 1024];
450 for (int i = 0; i < slen; i += bytes.Length) {
451 int x = reader.Read (bytes, 0, bytes.Length);
452 ms.Write (bytes, 0, x);
454 ms.Seek (0, SeekOrigin.Begin);
467 public IDictionaryEnumerator GetEnumerator () {
469 throw new InvalidOperationException("ResourceReader is closed.");
472 return new ResourceEnumerator (this);
476 IEnumerator IEnumerable.GetEnumerator ()
478 return ((IResourceReader) this).GetEnumerator();
481 void IDisposable.Dispose ()
486 private void Dispose (bool disposing)
500 internal class ResourceEnumerator : IDictionaryEnumerator
502 private ResourceReader reader;
503 private int index = -1;
504 private bool finished = false;
506 internal ResourceEnumerator(ResourceReader readerToEnumerate){
507 reader = readerToEnumerate;
510 public virtual DictionaryEntry Entry
513 if (reader.reader == null)
514 throw new InvalidOperationException("ResourceReader is closed.");
516 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
518 DictionaryEntry entry = new DictionaryEntry();
525 public virtual object Key
528 if (reader.reader == null)
529 throw new InvalidOperationException("ResourceReader is closed.");
531 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
532 return (reader.ResourceName(index));
536 public virtual object Value
539 if (reader.reader == null)
540 throw new InvalidOperationException("ResourceReader is closed.");
542 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
543 return(reader.ResourceValue(index));
548 public UnmanagedMemoryStream ValueAsStream
551 if (reader.reader == null)
552 throw new InvalidOperationException("ResourceReader is closed.");
554 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
555 return(reader.ResourceValueAsStream((string) Key, index));
560 public virtual object Current
563 /* Entry does the checking, no
564 * need to repeat it here
570 public virtual bool MoveNext ()
572 if (reader.reader == null)
573 throw new InvalidOperationException("ResourceReader is closed.");
578 if (++index < reader.resourceCount){
587 public void Reset () {
588 if (reader.reader == null)
589 throw new InvalidOperationException("ResourceReader is closed.");
593 } // internal class ResourceEnumerator
594 } // public sealed class ResourceReader
595 } // namespace System.Resources