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