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