2005-01-31 Zoltan Varga <vargaz@freemail.hu>
[mono.git] / mcs / class / ICSharpCode.SharpZipLib / ICSharpCode.SharpZipLib / Zip / ZipFile.cs
1 // ZipFile.cs\r
2 // Copyright (C) 2001 Mike Krueger\r
3 //\r
4 // This file was translated from java, it was part of the GNU Classpath\r
5 // Copyright (C) 2001 Free Software Foundation, Inc.\r
6 //\r
7 // This program is free software; you can redistribute it and/or\r
8 // modify it under the terms of the GNU General Public License\r
9 // as published by the Free Software Foundation; either version 2\r
10 // of the License, or (at your option) any later version.\r
11 //\r
12 // This program is distributed in the hope that it will be useful,\r
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of\r
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
15 // GNU General Public License for more details.\r
16 //\r
17 // You should have received a copy of the GNU General Public License\r
18 // along with this program; if not, write to the Free Software\r
19 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\r
20 //\r
21 // Linking this library statically or dynamically with other modules is\r
22 // making a combined work based on this library.  Thus, the terms and\r
23 // conditions of the GNU General Public License cover the whole\r
24 // combination.\r
25 // \r
26 // As a special exception, the copyright holders of this library give you\r
27 // permission to link this library with independent modules to produce an\r
28 // executable, regardless of the license terms of these independent\r
29 // modules, and to copy and distribute the resulting executable under\r
30 // terms of your choice, provided that you also meet, for each linked\r
31 // independent module, the terms and conditions of the license of that\r
32 // module.  An independent module is a module which is not derived from\r
33 // or based on this library.  If you modify this library, you may extend\r
34 // this exception to your version of the library, but you are not\r
35 // obligated to do so.  If you do not wish to do so, delete this\r
36 // exception statement from your version.\r
37 \r
38 using System;\r
39 using System.Collections;\r
40 using System.IO;\r
41 using System.Text;\r
42 \r
43 using ICSharpCode.SharpZipLib.Zip.Compression.Streams;\r
44 using ICSharpCode.SharpZipLib.Zip.Compression;\r
45 \r
46 namespace ICSharpCode.SharpZipLib.Zip \r
47 {\r
48         \r
49         /// <summary>\r
50         /// This class represents a Zip archive.  You can ask for the contained\r
51         /// entries, or get an input stream for a file entry.  The entry is\r
52         /// automatically decompressed.\r
53         /// \r
54         /// This class is thread safe:  You can open input streams for arbitrary\r
55         /// entries in different threads.\r
56         /// \r
57         /// author of the original java version : Jochen Hoenicke\r
58         /// </summary>\r
59         /// <example>\r
60         /// using System;\r
61         /// using System.Text;\r
62         /// using System.Collections;\r
63         /// using System.IO;\r
64         /// \r
65         /// using NZlib.Zip;\r
66         /// \r
67         /// class MainClass\r
68         /// {\r
69         ///     static public void Main(string[] args)\r
70         ///     {\r
71         ///             ZipFile zFile = new ZipFile(args[0]);\r
72         ///             //Console.WriteLine("Listing of : " + zFile.Name);\r
73         ///             //Console.WriteLine("");\r
74         ///             //Console.WriteLine("Raw Size    Size      Date     Time     Name");\r
75         ///             //Console.WriteLine("--------  --------  --------  ------  ---------");\r
76         ///             foreach (ZipEntry e in zFile) {\r
77         ///                     DateTime d = e.DateTime;\r
78         ///                     //Console.WriteLine("{0, -10}{1, -10}{2}  {3}   {4}", e.Size, e.CompressedSize,\r
79         ///                                                                         d.ToString("dd-MM-yy"), d.ToString("t"),\r
80         ///                                                                         e.Name);\r
81         ///             }\r
82         ///     }\r
83         /// }\r
84         /// </example>\r
85         public class ZipFile : IEnumerable\r
86         {\r
87                 string     name;\r
88                 string     comment;\r
89                 Stream     baseStream;\r
90                 ZipEntry[] entries;\r
91                 \r
92                 /// <summary>\r
93                 /// Opens a Zip file with the given name for reading.\r
94                 /// </summary>\r
95                 /// <exception name="System.IO.IOException">\r
96                 /// IOException if a i/o error occured.\r
97                 /// </exception>\r
98                 /// <exception name="ICSharpCode.SharpZipLib.ZipException">\r
99                 /// if the file doesn't contain a valid zip archive.\r
100                 /// </exception>\r
101                 public ZipFile(string name) : this(File.OpenRead(name))\r
102                 {\r
103                 }\r
104                 \r
105                 /// <summary>\r
106                 /// Opens a Zip file reading the given FileStream\r
107                 /// </summary>\r
108                 /// <exception name="System.IO.IOException">\r
109                 /// IOException if a i/o error occured.\r
110                 /// </exception>\r
111                 /// <exception name="ICSharpCode.SharpZipLib.ZipException">\r
112                 /// if the file doesn't contain a valid zip archive.\r
113                 /// </exception>\r
114                 public ZipFile(FileStream file)\r
115                 {\r
116                         this.baseStream  = file;\r
117                         this.name = file.Name;\r
118                         ReadEntries();\r
119                 }\r
120                 \r
121                 /// <summary>\r
122                 /// Opens a Zip file reading the given Stream\r
123                 /// </summary>\r
124                 /// <exception name="System.IO.IOException">\r
125                 /// IOException if a i/o error occured.\r
126                 /// </exception>\r
127                 /// <exception name="ICSharpCode.SharpZipLib.ZipException">\r
128                 /// if the file doesn't contain a valid zip archive.\r
129                 /// </exception>\r
130                 public ZipFile(Stream baseStream)\r
131                 {\r
132                         this.baseStream  = baseStream;\r
133                         this.name = null;\r
134                         ReadEntries();\r
135                 }\r
136                 \r
137                 \r
138                 /// <summary>\r
139                 /// Read an unsigned short in little endian byte order.\r
140                 /// </summary>\r
141                 /// <exception name="System.IO.IOException">\r
142                 /// if a i/o error occured.\r
143                 /// </exception>\r
144                 /// <exception name="System.IO.EndOfStreamException">\r
145                 /// if the file ends prematurely\r
146                 /// </exception>\r
147                 int ReadLeShort()\r
148                 {\r
149                         return baseStream.ReadByte() | baseStream.ReadByte() << 8;\r
150                 }\r
151                 \r
152                 /// <summary>\r
153                 /// Read an int in little endian byte order.\r
154                 /// </summary>\r
155                 /// <exception name="System.IO.IOException">\r
156                 /// if a i/o error occured.\r
157                 /// </exception>\r
158                 /// <exception name="System.IO.EndOfStreamException">\r
159                 /// if the file ends prematurely\r
160                 /// </exception>\r
161                 int ReadLeInt()\r
162                 {\r
163                         return ReadLeShort() | ReadLeShort() << 16;\r
164                 }\r
165                 \r
166                 /// <summary>\r
167                 /// Read the central directory of a zip file and fill the entries\r
168                 /// array.  This is called exactly once by the constructors.\r
169                 /// </summary>\r
170                 /// <exception name="System.IO.IOException">\r
171                 /// if a i/o error occured.\r
172                 /// </exception>\r
173                 /// <exception name="ICSharpCode.SharpZipLib.ZipException">\r
174                 /// if the central directory is malformed\r
175                 /// </exception>\r
176                 void ReadEntries()\r
177                 {\r
178                         /* Search for the End Of Central Directory.  When a zip comment is\r
179                         * present the directory may start earlier.\r
180                         * FIXME: This searches the whole file in a very slow manner if the\r
181                         * file isn't a zip file.\r
182                         */\r
183                         long pos = baseStream.Length - ZipConstants.ENDHDR;\r
184                         do {\r
185                                 if (pos < 0) {\r
186                                         throw new ZipException("central directory not found, probably not a zip file");\r
187                                 }\r
188                                 baseStream.Seek(pos--, SeekOrigin.Begin);\r
189                         } while (ReadLeInt() != ZipConstants.ENDSIG);\r
190                         \r
191                         long oldPos = baseStream.Position;\r
192                         baseStream.Position += ZipConstants.ENDTOT - ZipConstants.ENDNRD;\r
193                         \r
194                         if (baseStream.Position - oldPos != ZipConstants.ENDTOT - ZipConstants.ENDNRD) {\r
195                                 throw new EndOfStreamException();\r
196                         }\r
197                         int count = ReadLeShort();\r
198                         \r
199                         oldPos = baseStream.Position;\r
200                         baseStream.Position += ZipConstants.ENDOFF - ZipConstants.ENDSIZ;\r
201                         \r
202                         if (baseStream.Position - oldPos != ZipConstants.ENDOFF - ZipConstants.ENDSIZ) {\r
203                                 throw new EndOfStreamException();\r
204                         }\r
205                         \r
206                         int centralOffset = ReadLeInt();\r
207                         \r
208                         // GET COMMENT SIZE (COMES AFTER CENTRALOFFSET) \r
209                         int commentSize = ReadLeShort(); \r
210                         byte[] zipComment = new byte[commentSize]; \r
211                         baseStream.Read(zipComment, 0, zipComment.Length); \r
212                         comment = ZipConstants.ConvertToString(zipComment); \r
213                         \r
214                         entries = new ZipEntry[count];\r
215                         baseStream.Seek(centralOffset, SeekOrigin.Begin);\r
216                         for (int i = 0; i < count; i++) {\r
217                                 if (ReadLeInt() != ZipConstants.CENSIG) {\r
218                                         throw new ZipException("Wrong Central Directory signature");\r
219                                 }\r
220                                 \r
221                                 oldPos = baseStream.Position;\r
222                                 baseStream.Position += ZipConstants.CENHOW - ZipConstants.CENVEM;\r
223                                 \r
224                                 if (baseStream.Position - oldPos != ZipConstants.CENHOW - ZipConstants.CENVEM) {\r
225                                         throw new EndOfStreamException();\r
226                                 }\r
227                                 int method = ReadLeShort();\r
228                                 int dostime = ReadLeInt();\r
229                                 int crc = ReadLeInt();\r
230                                 int csize = ReadLeInt();\r
231                                 int size = ReadLeInt();\r
232                                 int nameLen = ReadLeShort();\r
233                                 int extraLen = ReadLeShort();\r
234                                 int commentLen = ReadLeShort();\r
235                                 \r
236                                 oldPos = baseStream.Position;\r
237                                 baseStream.Position += ZipConstants.CENOFF - ZipConstants.CENDSK;\r
238                                 if (baseStream.Position - oldPos != ZipConstants.CENOFF - ZipConstants.CENDSK) {\r
239                                         throw new EndOfStreamException();\r
240                                 }\r
241                                 int offset = ReadLeInt();\r
242                                 \r
243                                 byte[] buffer = new byte[Math.Max(nameLen, commentLen)];\r
244                                 \r
245                                 baseStream.Read(buffer, 0, nameLen);\r
246                                 string name = ZipConstants.ConvertToString(buffer);\r
247                                 \r
248                                 ZipEntry entry = new ZipEntry(name);\r
249                                 entry.CompressionMethod = (CompressionMethod)method;\r
250                                 entry.Crc = crc & 0xffffffffL;\r
251                                 entry.Size = size & 0xffffffffL;\r
252                                 entry.CompressedSize = csize & 0xffffffffL;\r
253                                 entry.DosTime = (uint)dostime;\r
254                                 if (extraLen > 0) {\r
255                                         byte[] extra = new byte[extraLen];\r
256                                         baseStream.Read(extra, 0, extraLen);\r
257                                         entry.ExtraData = extra;\r
258                                 }\r
259                                 if (commentLen > 0) {\r
260                                         baseStream.Read(buffer, 0, commentLen);\r
261                                         entry.Comment = ZipConstants.ConvertToString(buffer);\r
262                                 }\r
263                                 entry.ZipFileIndex = i;\r
264                                 entry.Offset = offset;\r
265                                 entries[i] = entry;\r
266                         }\r
267                 }\r
268                 \r
269                 /// <summary>\r
270                 /// Closes the ZipFile.  This also closes all input streams given by\r
271                 /// this class.  After this is called, no further method should be\r
272                 /// called.\r
273                 /// </summary>\r
274                 /// <exception name="System.IO.IOException">\r
275                 /// if a i/o error occured.\r
276                 /// </exception>\r
277                 public void Close()\r
278                 {\r
279                         entries = null;\r
280                         lock(baseStream) {\r
281                                 baseStream.Close();\r
282                         }\r
283                 }\r
284                 \r
285                 /// <summary>\r
286                 /// Returns an IEnumerator of all Zip entries in this Zip file.\r
287                 /// </summary>\r
288                 public IEnumerator GetEnumerator()\r
289                 {\r
290                         if (entries == null) {\r
291                                 throw new InvalidOperationException("ZipFile has closed");\r
292                         }\r
293                         \r
294                         return new ZipEntryEnumeration(entries);\r
295                 }\r
296                 \r
297                 int GetEntryIndex(string name)\r
298                 {\r
299                         for (int i = 0; i < entries.Length; i++) {\r
300                                 if (name.Equals(entries[i].Name)) {\r
301                                         return i;\r
302                                 }\r
303                         }\r
304                         return -1; // ok\r
305                 }\r
306                 \r
307                 /// <summary>\r
308                 /// Searches for a zip entry in this archive with the given name.\r
309                 /// </summary>\r
310                 /// <param name="name">\r
311                 /// the name. May contain directory components separated by slashes ('/').\r
312                 /// </param>\r
313                 /// <returns>\r
314                 /// the zip entry, or null if no entry with that name exists.\r
315                 /// </returns>\r
316                 public ZipEntry GetEntry(string name)\r
317                 {\r
318                         if (entries == null) {\r
319                                 throw new InvalidOperationException("ZipFile has closed");\r
320                         }\r
321                         int index = GetEntryIndex(name);\r
322                         return index >= 0 ? (ZipEntry) entries[index].Clone() : null;\r
323                 }\r
324                 \r
325                 /// <summary>\r
326                 /// Checks, if the local header of the entry at index i matches the\r
327                 /// central directory, and returns the offset to the data.\r
328                 /// </summary>\r
329                 /// <returns>\r
330                 /// the start offset of the (compressed) data.\r
331                 /// </returns>\r
332                 /// <exception name="System.IO.IOException">\r
333                 /// if a i/o error occured.\r
334                 /// </exception>\r
335                 /// <exception name="ICSharpCode.SharpZipLib.ZipException">\r
336                 /// if the local header doesn't match the central directory header\r
337                 /// </exception>\r
338                 long CheckLocalHeader(ZipEntry entry)\r
339                 {\r
340                         lock(baseStream) {\r
341                                 baseStream.Seek(entry.Offset, SeekOrigin.Begin);\r
342                                 if (ReadLeInt() != ZipConstants.LOCSIG) {\r
343                                         throw new ZipException("Wrong Local header signature");\r
344                                 }\r
345                                 \r
346                                 /* skip version and flags */\r
347                                 long oldPos = baseStream.Position;\r
348                                 baseStream.Position += ZipConstants.LOCHOW - ZipConstants.LOCVER;\r
349                                 if (baseStream.Position - oldPos != ZipConstants.LOCHOW - ZipConstants.LOCVER) {\r
350                                         throw new EndOfStreamException();\r
351                                 }\r
352                                 \r
353                                 if (entry.CompressionMethod != (CompressionMethod)ReadLeShort()) {\r
354                                         throw new ZipException("Compression method mismatch");\r
355                                 }\r
356                                 \r
357                                 /* Skip time, crc, size and csize */\r
358                                 oldPos = baseStream.Position;\r
359                                 baseStream.Position += ZipConstants.LOCNAM - ZipConstants.LOCTIM;\r
360                                 \r
361                                 if (baseStream.Position - oldPos != ZipConstants.LOCNAM - ZipConstants.LOCTIM) {\r
362                                         throw new EndOfStreamException();\r
363                                 }\r
364                                 \r
365                                 if (entry.Name.Length != ReadLeShort()) {\r
366                                         throw new ZipException("file name length mismatch");\r
367                                 }\r
368                                 \r
369                                 int extraLen = entry.Name.Length + ReadLeShort();\r
370                                 return entry.Offset + ZipConstants.LOCHDR + extraLen;\r
371                         }\r
372                 }\r
373                 \r
374                 /// <summary>\r
375                 /// Creates an input stream reading the given zip entry as\r
376                 /// uncompressed data.  Normally zip entry should be an entry\r
377                 /// returned by GetEntry().\r
378                 /// </summary>\r
379                 /// <returns>\r
380                 /// the input stream.\r
381                 /// </returns>\r
382                 /// <exception name="System.IO.IOException">\r
383                 /// if a i/o error occured.\r
384                 /// </exception>\r
385                 /// <exception name="ICSharpCode.SharpZipLib.ZipException">\r
386                 /// if the Zip archive is malformed.\r
387                 /// </exception>\r
388                 public Stream GetInputStream(ZipEntry entry)\r
389                 {\r
390                         if (entries == null) {\r
391                                 throw new InvalidOperationException("ZipFile has closed");\r
392                         }\r
393                         \r
394                         int index = entry.ZipFileIndex;\r
395                         if (index < 0 || index >= entries.Length || entries[index].Name != entry.Name) {\r
396                                 index = GetEntryIndex(entry.Name);\r
397                                 if (index < 0) {\r
398                                         throw new IndexOutOfRangeException();\r
399                                 }\r
400                         }\r
401                         \r
402                         long start = CheckLocalHeader(entries[index]);\r
403                         CompressionMethod method = entries[index].CompressionMethod;\r
404                         Stream istr = new PartialInputStream(baseStream, start, entries[index].CompressedSize);\r
405                         switch (method) {\r
406                                 case CompressionMethod.Stored:\r
407                                         return istr;\r
408                                 case CompressionMethod.Deflated:\r
409                                         return new InflaterInputStream(istr, new Inflater(true));\r
410                                 default:\r
411                                         throw new ZipException("Unknown compression method " + method);\r
412                         }\r
413                 }\r
414                 \r
415                 /// <summary>\r
416                 /// The comment for the whole zip file.\r
417                 /// </summary>\r
418                 public string ZipFileComment {\r
419                         get {\r
420                                 return comment;\r
421                         }\r
422                 }\r
423                 \r
424                 /// <summary>\r
425                 /// Returns the name of this zip file.\r
426                 /// </summary>\r
427                 public string Name {\r
428                         get {\r
429                                 return name;\r
430                         }\r
431                 }\r
432                 \r
433                 /// <summary>\r
434                 /// Returns the number of entries in this zip file.\r
435                 /// </summary>\r
436                 public int Size {\r
437                         get {\r
438                                 try {\r
439                                         return entries.Length;\r
440                                 } catch (Exception) {\r
441                                         throw new InvalidOperationException("ZipFile has closed");\r
442                                 }\r
443                         }\r
444                 }\r
445                 \r
446                 class ZipEntryEnumeration : IEnumerator\r
447                 {\r
448                         ZipEntry[] array;\r
449                         int ptr = -1;\r
450                         \r
451                         public ZipEntryEnumeration(ZipEntry[] arr)\r
452                         {\r
453                                 array = arr;\r
454                         }\r
455                         \r
456                         public object Current {\r
457                                 get {\r
458                                         return array[ptr];\r
459                                 }\r
460                         }\r
461                         \r
462                         public void Reset()\r
463                         {\r
464                                 ptr = -1;\r
465                         }\r
466                         \r
467                         public bool MoveNext() \r
468                         {\r
469                                 return (++ptr < array.Length);\r
470                         }\r
471                 }\r
472                 \r
473                 class PartialInputStream : InflaterInputStream\r
474                 {\r
475                         Stream baseStream;\r
476                         long filepos, end;\r
477                         \r
478                         public PartialInputStream(Stream baseStream, long start, long len) : base(baseStream)\r
479                         {\r
480                                 this.baseStream = baseStream;\r
481                                 filepos = start;\r
482                                 end = start + len;\r
483                         }\r
484                         \r
485                         public override int Available \r
486                         {\r
487                                 get {\r
488                                         long amount = end - filepos;\r
489                                         if (amount > Int32.MaxValue) {\r
490                                                 return Int32.MaxValue;\r
491                                         }\r
492                                         \r
493                                         return (int) amount;\r
494                                 }\r
495                         }\r
496                         \r
497                         public override int ReadByte()\r
498                         {\r
499                                 if (filepos == end) {\r
500                                         return -1; //ok\r
501                                 }\r
502                                 \r
503                                 lock(baseStream) {\r
504                                         baseStream.Seek(filepos++, SeekOrigin.Begin);\r
505                                         return baseStream.ReadByte();\r
506                                 }\r
507                         }\r
508                         \r
509                         public override int Read(byte[] b, int off, int len)\r
510                         {\r
511                                 if (len > end - filepos) {\r
512                                         len = (int) (end - filepos);\r
513                                         if (len == 0) {\r
514                                                 return 0;\r
515                                         }\r
516                                 }\r
517                                 lock(baseStream) {\r
518                                         baseStream.Seek(filepos, SeekOrigin.Begin);\r
519                                         int count = baseStream.Read(b, off, len);\r
520                                         if (count > 0) {\r
521                                                 filepos += len;\r
522                                         }\r
523                                         return count;\r
524                                 }\r
525                         }\r
526                         \r
527                         public long SkipBytes(long amount)\r
528                         {\r
529                                 if (amount < 0) {\r
530                                         throw new ArgumentOutOfRangeException();\r
531                                 }\r
532                                 if (amount > end - filepos) {\r
533                                         amount = end - filepos;\r
534                                 }\r
535                                 filepos += amount;\r
536                                 return amount;\r
537                         }\r
538                 }\r
539         }\r
540 }\r