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