[corlib] Remove ResourceCloseHandler usage
[mono.git] / mcs / class / corlib / System.Resources / ResourceReader.cs
1 //
2 // System.Resources.ResourceReader.cs
3 //
4 // Authors: 
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>
11 //
12 // (C) 2001, 2002 Ximian Inc, http://www.ximian.com
13 // Copyright (C) 2004-2005,2007-2008 Novell, Inc (http://www.novell.com)
14 //
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:
22 // 
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
25 // 
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.
33 //
34
35 using System.Collections;
36 using System.Resources;
37 using System.IO;
38 using System.Text;
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;
44
45 namespace System.Resources
46 {
47         internal enum PredefinedResourceType
48         {
49                 Null            = 0,
50                 String          = 1,
51                 Bool            = 2,
52                 Char            = 3,
53                 Byte            = 4,
54                 SByte           = 5,
55                 Int16           = 6,
56                 UInt16          = 7,
57                 Int32           = 8,
58                 UInt32          = 9,
59                 Int64           = 10,
60                 UInt64          = 11,
61                 Single          = 12,
62                 Double          = 13,
63                 Decimal         = 14,
64                 DateTime        = 15,
65                 TimeSpan        = 16,
66                 ByteArray       = 32,
67                 Stream          = 33,
68                 FistCustom      = 64
69         }
70
71         [System.Runtime.InteropServices.ComVisible (true)]
72         public sealed class ResourceReader : IResourceReader, IEnumerable, IDisposable
73         {
74                 struct ResourceInfo
75                 {
76                         public readonly long ValuePosition;
77                         public readonly string ResourceName;
78                         public readonly int TypeIndex;
79                         
80                         public ResourceInfo (string resourceName, long valuePosition, int type_index)
81                         {
82                                 ValuePosition = valuePosition;
83                                 ResourceName = resourceName;
84                                 TypeIndex = type_index;
85                         }
86                 }
87
88                 struct ResourceCacheItem
89                 {
90                         public readonly string ResourceName;
91                         public readonly object ResourceValue;
92
93                         public ResourceCacheItem (string name, object value)
94                         {
95                                 ResourceName = name;
96                                 ResourceValue = value;
97                         }
98                 }
99
100                 unsafe class HGlobalUnmanagedMemoryStream : UnmanagedMemoryStream
101                 {
102                         IntPtr ptr;
103
104                         public HGlobalUnmanagedMemoryStream (byte* pointer, long length, IntPtr ptr)
105                                 : base (pointer, length, length, FileAccess.ReadWrite)
106                         {
107                                 this.ptr = ptr;
108                         }
109
110                         protected override void Dispose (bool disposing)
111                         {
112                                 if (_isOpen) {
113                                         Marshal.FreeHGlobal (ptr);
114                                 }
115
116                                 base.Dispose (disposing);
117                         }
118                 }
119                 
120                 BinaryReader reader;
121                 object readerLock = new object ();
122                 IFormatter formatter;
123                 internal int resourceCount = 0;
124                 int typeCount = 0;
125                 string[] typeNames;
126                 int[] hashes;
127                 ResourceInfo[] infos;
128                 int dataSectionOffset;
129                 long nameSectionOffset;
130                 int resource_ver;
131                 ResourceCacheItem[] cache;
132                 object cache_lock = new object ();
133                 
134                 // Constructors
135                 [SecurityPermission (SecurityAction.LinkDemand, SerializationFormatter = true)]
136                 public ResourceReader (Stream stream)
137                 {
138                         if (stream == null)
139                                 throw new ArgumentNullException ("stream");
140                         
141                         if (!stream.CanRead)
142                                 throw new ArgumentException ("Stream was not readable.");
143
144                         reader = new BinaryReader(stream, Encoding.UTF8);
145                         formatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File|StreamingContextStates.Persistence));
146                         
147                         ReadHeaders();
148                 }
149                 
150                 public ResourceReader (string fileName)
151                 {
152                         reader = new BinaryReader (new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read));
153                         formatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File|StreamingContextStates.Persistence));
154
155                         ReadHeaders();
156                 }
157                 
158                 /* Read the ResourceManager header and the
159                  * ResourceReader header.
160                  */
161                 private void ReadHeaders()
162                 {
163                         try {
164                                 int manager_magic = reader.ReadInt32();
165
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));
168
169                                 int manager_ver = reader.ReadInt32();
170                                 int manager_len = reader.ReadInt32();
171                                 
172                                 /* We know how long the header is, even if
173                                  * the version number is too new
174                                  */
175                                 if(manager_ver > ResourceManager.HeaderVersionNumber) {
176                                         reader.BaseStream.Seek(manager_len, SeekOrigin.Current);
177                                 } else {
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);
181                                         }
182                                         
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);
186                                         }
187                                 }
188
189                                 /* Now read the ResourceReader header */
190                                 resource_ver = reader.ReadInt32();
191
192                                 if(resource_ver != 1 && resource_ver != 2) {
193                                         throw new NotSupportedException("This .resources file requires unsupported set class version: " + resource_ver.ToString());
194                                 }
195
196                                 resourceCount = reader.ReadInt32();
197                                 typeCount = reader.ReadInt32();
198                                 
199                                 typeNames=new string[typeCount];
200
201                                 for(int i=0; i<typeCount; i++) {
202                                         typeNames[i]=reader.ReadString();
203                                 }
204
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
210                                  * boundary.
211                                  */
212
213                                 int pad_align=(int)(reader.BaseStream.Position & 7);
214                                 int pad_chars=0;
215
216                                 if(pad_align!=0) {
217                                         pad_chars=8-pad_align;
218                                 }
219
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)");
224                                         }
225                                 }
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)
234                                  */
235                                 hashes=new int[resourceCount];
236                                 for(int i=0; i<resourceCount; i++) {
237                                         hashes[i]=reader.ReadInt32();
238                                 }
239                                 
240                                 /* Read in the virtual offsets for
241                                  * each resource name
242                                  */
243                                 long[] positions = new long [resourceCount];
244                                 for(int i = 0; i < resourceCount; i++)
245                                         positions [i] = reader.ReadInt32();
246                                 
247                                 dataSectionOffset = reader.ReadInt32();
248                                 nameSectionOffset = reader.BaseStream.Position;
249
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);
255                                 
256                                 positions = null;
257                         } catch(EndOfStreamException e) {
258                                 throw new ArgumentException("Stream is not a valid .resources file!  It was possibly truncated.", e);
259                         }
260                 }
261
262                 void CreateResourceInfo (long position, ref ResourceInfo info)
263                 {
264                         long pos = position + nameSectionOffset;
265
266                         reader.BaseStream.Seek (pos, SeekOrigin.Begin);
267
268                         // Resource name
269                         int len = Read7BitEncodedInt ();
270                         byte[] str = new byte [len];
271                         
272                         reader.Read (str, 0, len);
273                         string resourceName = Encoding.Unicode.GetString (str);
274
275                         long data_offset = reader.ReadInt32 () + dataSectionOffset;
276                         reader.BaseStream.Seek (data_offset, SeekOrigin.Begin);
277                         int type_index = Read7BitEncodedInt ();
278                         
279                         info = new ResourceInfo (resourceName, reader.BaseStream.Position, type_index);
280                 }
281                 
282                 /* Cut and pasted from BinaryReader, because it's
283                  * 'protected' there
284                  */
285                 private int Read7BitEncodedInt() {
286                         int ret = 0;
287                         int shift = 0;
288                         byte b;
289
290                         do {
291                                 b = reader.ReadByte();
292                                 
293                                 ret = ret | ((b & 0x7f) << shift);
294                                 shift += 7;
295                         } while ((b & 0x80) == 0x80);
296
297                         return ret;
298                 }
299
300                 object ReadValueVer2 (int type_index)
301                 {
302                         switch ((PredefinedResourceType)type_index)
303                         {
304                                 case PredefinedResourceType.Null:
305                                         return null;
306
307                                 case PredefinedResourceType.String:
308                                         return reader.ReadString();
309
310                                 case PredefinedResourceType.Bool:
311                                         return reader.ReadBoolean ();
312
313                                 case PredefinedResourceType.Char:
314                                         return (char)reader.ReadUInt16();
315
316                                 case PredefinedResourceType.Byte:
317                                         return reader.ReadByte();
318
319                                 case PredefinedResourceType.SByte:
320                                         return reader.ReadSByte();
321
322                                 case PredefinedResourceType.Int16:
323                                         return reader.ReadInt16();
324
325                                 case PredefinedResourceType.UInt16:
326                                         return reader.ReadUInt16();
327
328                                 case PredefinedResourceType.Int32:
329                                         return reader.ReadInt32();
330
331                                 case PredefinedResourceType.UInt32:
332                                         return reader.ReadUInt32();
333
334                                 case PredefinedResourceType.Int64:
335                                         return reader.ReadInt64();
336
337                                 case PredefinedResourceType.UInt64:
338                                         return reader.ReadUInt64();
339
340                                 case PredefinedResourceType.Single:
341                                         return reader.ReadSingle();
342
343                                 case PredefinedResourceType.Double:
344                                         return reader.ReadDouble();
345
346                                 case PredefinedResourceType.Decimal:
347                                         return reader.ReadDecimal();
348
349                                 case PredefinedResourceType.DateTime:
350                                         return new DateTime(reader.ReadInt64());
351
352                                 case PredefinedResourceType.TimeSpan:
353                                         return new TimeSpan(reader.ReadInt64());
354
355                                 case PredefinedResourceType.ByteArray:
356                                         return reader.ReadBytes (reader.ReadInt32 ());
357
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);
363                         }
364
365                         type_index -= (int)PredefinedResourceType.FistCustom;
366                         return ReadNonPredefinedValue (Type.GetType (typeNames[type_index], true));
367                 }
368
369                 object ReadValueVer1 (Type type)
370                 {
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()));
400
401                         return ReadNonPredefinedValue(type);
402                 }
403
404                 // TODO: Add security checks
405                 object ReadNonPredefinedValue (Type exp_type)
406                 {
407                         object obj=formatter.Deserialize(reader.BaseStream);
408                         if(obj.GetType() != exp_type) {
409                                 /* We got a bogus
410                                                  * object.  This
411                                                  * exception is the
412                                                  * best match I could
413                                                  * find.  (.net seems
414                                                  * to throw
415                                                  * BadImageFormatException,
416                                                  * which the docs
417                                                  * state is used when
418                                                  * file or dll images
419                                                  * cant be loaded by
420                                                  * the runtime.)
421                                                  */
422                                 throw new InvalidOperationException("Deserialized object is wrong type");
423                         }
424                         return obj;
425                 }               
426
427                 void LoadResourceValues (ResourceCacheItem[] store)
428                 {
429                         ResourceInfo ri;
430                         object value;
431                         
432                         lock (readerLock) {
433                                 for (int i = 0; i < resourceCount; i++) {
434                                         ri = infos [i];
435                                         if (ri.TypeIndex == -1) {
436                                                 store [i] = new ResourceCacheItem (ri.ResourceName, null);
437                                                 continue;
438                                         }
439
440                                         reader.BaseStream.Seek (ri.ValuePosition, SeekOrigin.Begin);
441                                         if (resource_ver == 2)
442                                                 value = ReadValueVer2 (ri.TypeIndex);
443                                         else
444                                                 value = ReadValueVer1 (Type.GetType (typeNames [ri.TypeIndex], true));
445
446                                         store [i] = new ResourceCacheItem (ri.ResourceName, value);
447                                 }
448                         }
449                 }
450                 
451                 internal UnmanagedMemoryStream ResourceValueAsStream (string name, int index)
452                 {
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));
456                         
457                         lock (readerLock) {
458                                 reader.BaseStream.Seek (ri.ValuePosition, SeekOrigin.Begin);
459                                 
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;
465                                 unsafe {
466                                         if (basePtrStream != null) {
467                                                 return new UnmanagedMemoryStream (basePtrStream.PositionPointer, slen);
468                                         } else {
469                                                 IntPtr ptr = Marshal.AllocHGlobal ((int) slen);
470                                                 byte* addr = (byte*) ptr.ToPointer ();
471                                                 UnmanagedMemoryStream ms = new HGlobalUnmanagedMemoryStream (addr, slen, ptr);
472
473                                                 byte [] bytes = new byte [slen < 1024 ? slen : 1024];
474                                                 while (slen > 0 ) {
475                                                         int x = reader.Read (bytes, 0, (int)Math.Min (bytes.Length, slen));
476
477                                                         if (x == 0)
478                                                                 throw new FormatException ("The resource data is corrupt. Resource stream ended");
479
480                                                         ms.Write (bytes, 0, x);
481                                                         slen -= x;
482                                                 }
483                                                 ms.Seek (0, SeekOrigin.Begin);
484                                                 return ms;
485                                         }
486                                 }
487                         }
488                 }
489
490                 public void Close ()
491                 {
492                         Dispose(true);
493                 }
494
495                 public void Dispose ()
496                 {
497                         Dispose(true);
498                 }
499                 
500                 public IDictionaryEnumerator GetEnumerator () {
501                         if (reader == null){
502                                 throw new InvalidOperationException("ResourceReader is closed.");
503                         }
504                         else {
505                                 return new ResourceEnumerator (this);
506                         }
507                 }
508                 
509                 IEnumerator IEnumerable.GetEnumerator ()
510                 {
511                         return ((IResourceReader) this).GetEnumerator();
512                 }
513
514                 public void GetResourceData (string resourceName, out string resourceType, out byte [] resourceData)
515                 {
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);
522                                         return;
523                                 }
524                         throw new ArgumentException (String.Format ("Specified resource not found: {0}", resourceName));
525                 }
526
527                 private void GetResourceDataAt (int index, out string resourceType, out byte [] data)
528                 {
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");
533                         
534                         lock (readerLock) {
535                                 reader.BaseStream.Seek (ri.ValuePosition, SeekOrigin.Begin);
536                                 long pos2 = reader.BaseStream.Position;
537
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];
545                                         }
546                                         else
547                                                 resourceType = "ResourceTypeCode." + (PredefinedResourceType) type_index;
548                                         ReadValueVer2 (type_index);
549                                 } else {
550                                         // resource ver 1 == untyped
551                                         resourceType = "ResourceTypeCode.Null";
552                                         ReadValueVer1 (Type.GetType (typeNames [type_index], true));
553                                 }
554
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);
560                         }
561                 }
562
563                 private void Dispose (bool disposing)
564                 {
565                         if(disposing) {
566                                 if(reader!=null) {
567                                         reader.Close();
568                                 }
569                         }
570                         
571                         reader=null;
572                         hashes=null;
573                         infos=null;
574                         typeNames=null;
575                         cache = null;
576                 }
577                 
578                 internal sealed class ResourceEnumerator : IDictionaryEnumerator
579                 {
580                         private ResourceReader reader;
581                         private int index = -1;
582                         private bool finished;
583                         
584                         internal ResourceEnumerator(ResourceReader readerToEnumerate)
585                         {
586                                 reader = readerToEnumerate;
587                                 FillCache ();
588                         }
589
590                         public int Index
591                         {
592                                 get { return index; }
593                         }
594
595                         public DictionaryEntry Entry {
596                                 get {
597                                         if (reader.reader == null)
598                                                 throw new InvalidOperationException("ResourceReader is closed.");
599                                         if (index < 0)
600                                                 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
601
602                                         return new DictionaryEntry(Key, Value);
603                                 }
604                         }
605                         
606                         public object Key
607                         {
608                                 get { 
609                                         if (reader.reader == null)
610                                                 throw new InvalidOperationException("ResourceReader is closed.");
611                                         if (index < 0)
612                                                 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
613
614                                         return reader.cache [index].ResourceName;
615                                 }
616                         }
617                         
618                         public object Value
619                         {
620                                 get { 
621                                         if (reader.reader == null)
622                                                 throw new InvalidOperationException("ResourceReader is closed.");
623                                         if (index < 0)
624                                                 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
625                                         return reader.cache [index].ResourceValue;
626                                 }
627                         }
628                         
629                         public UnmanagedMemoryStream ValueAsStream
630                         {
631                                 get {
632                                         if (reader.reader == null)
633                                                 throw new InvalidOperationException("ResourceReader is closed.");
634                                         if (index < 0)
635                                                 throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
636                                         return(reader.ResourceValueAsStream((string) Key, index));
637                                 }
638                         }
639                         
640                         public object Current
641                         {
642                                 get {
643                                         /* Entry does the checking, no
644                                          * need to repeat it here
645                                          */
646                                         return Entry; 
647                                 }
648                         }
649                         
650                         public bool MoveNext ()
651                         {
652                                 if (reader.reader == null)
653                                         throw new InvalidOperationException("ResourceReader is closed.");
654                                 if (finished) {
655                                         return false;
656                                 }
657                                 
658                                 if (++index < reader.resourceCount){
659                                         return true;
660                                 }
661
662                                 finished=true;
663                                 return false;
664                         }
665                         
666                         public void Reset () {
667                                 if (reader.reader == null)
668                                         throw new InvalidOperationException("ResourceReader is closed.");
669                                 index = -1;
670                                 finished = false;
671                         }
672
673                         void FillCache ()
674                         {
675                                 if (reader.cache != null)
676                                         return;
677                                 
678                                 lock (reader.cache_lock) {
679                                         if (reader.cache != null)
680                                                 return;
681                                         
682                                         ResourceCacheItem[] resources = new ResourceCacheItem [reader.resourceCount];                           
683                                         reader.LoadResourceValues (resources);
684                                         reader.cache = resources;
685                                 }
686                         }
687                 } // internal class ResourceEnumerator
688         }  // public sealed class ResourceReader
689 } // namespace System.Resources