Add SharpZiplib
[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         /// <summary>\r
49         /// This class represents a Zip archive.  You can ask for the contained\r
50         /// entries, or get an input stream for a file entry.  The entry is\r
51         /// automatically decompressed.\r
52         /// \r
53         /// This class is thread safe:  You can open input streams for arbitrary\r
54         /// entries in different threads.\r
55         /// \r
56         /// author of the original java version : Jochen Hoenicke\r
57         /// </summary>\r
58         /// <example>\r
59         /// using System;\r
60         /// using System.Text;\r
61         /// using System.Collections;\r
62         /// using System.IO;\r
63         /// \r
64         /// using NZlib.Zip;\r
65         /// \r
66         /// class MainClass\r
67         /// {\r
68         ///     static public void Main(string[] args)\r
69         ///     {\r
70         ///             ZipFile zFile = new ZipFile(args[0]);\r
71         ///             Console.WriteLine("Listing of : " + zFile.Name);\r
72         ///             Console.WriteLine("");\r
73         ///             Console.WriteLine("Raw Size    Size      Date     Time     Name");\r
74         ///             Console.WriteLine("--------  --------  --------  ------  ---------");\r
75         ///             foreach (ZipEntry e in zFile) {\r
76         ///                     DateTime d = e.DateTime;\r
77         ///                     Console.WriteLine("{0, -10}{1, -10}{2}  {3}   {4}", e.Size, e.CompressedSize,\r
78         ///                                                                         d.ToString("dd-MM-yy"), d.ToString("t"),\r
79         ///                                                                         e.Name);\r
80         ///             }\r
81         ///     }\r
82         /// }\r
83         /// </example>\r
84         public class ZipFile : IEnumerable\r
85         {\r
86                 string     name;\r
87                 Stream     baseStream;\r
88                 ZipEntry[] entries;\r
89                 \r
90                 /// <summary>\r
91                 /// Opens a Zip file with the given name for reading.\r
92                 /// </summary>\r
93                 /// <exception name="System.IO.IOException">\r
94                 /// IOException if a i/o error occured.\r
95                 /// </exception>\r
96                 /// <exception name="ICSharpCode.SharpZipLib.ZipException">\r
97                 /// if the file doesn't contain a valid zip archive.\r
98                 /// </exception>\r
99                 public ZipFile(string name) : this(File.OpenRead(name))\r
100                 {\r
101                 }\r
102                 \r
103                 /// <summary>\r
104                 /// Opens a Zip file reading the given FileStream\r
105                 /// </summary>\r
106                 /// <exception name="System.IO.IOException">\r
107                 /// IOException if a i/o error occured.\r
108                 /// </exception>\r
109                 /// <exception name="ICSharpCode.SharpZipLib.ZipException">\r
110                 /// if the file doesn't contain a valid zip archive.\r
111                 /// </exception>\r
112                 public ZipFile(FileStream file)\r
113                 {\r
114                         this.baseStream  = file;\r
115                         this.name = file.Name;\r
116                         ReadEntries();\r
117                 }\r
118                 \r
119                 /// <summary>\r
120                 /// Opens a Zip file reading the given Stream\r
121                 /// </summary>\r
122                 /// <exception name="System.IO.IOException">\r
123                 /// IOException if a i/o error occured.\r
124                 /// </exception>\r
125                 /// <exception name="ICSharpCode.SharpZipLib.ZipException">\r
126                 /// if the file doesn't contain a valid zip archive.\r
127                 /// </exception>\r
128                 public ZipFile(Stream baseStream)\r
129                 {\r
130                         this.baseStream  = baseStream;\r
131                         this.name = null;\r
132                         ReadEntries();\r
133                 }\r
134                 \r
135                 \r
136                 /// <summary>\r
137                 /// Read an unsigned short in little endian byte order.\r
138                 /// </summary>\r
139                 /// <exception name="System.IO.IOException">\r
140                 /// if a i/o error occured.\r
141                 /// </exception>\r
142                 /// <exception name="System.IO.EndOfStreamException">\r
143                 /// if the file ends prematurely\r
144                 /// </exception>\r
145                 int ReadLeShort()\r
146                 {\r
147                         return baseStream.ReadByte() | baseStream.ReadByte() << 8;\r
148                 }\r
149                 \r
150                 /// <summary>\r
151                 /// Read an int in little endian byte order.\r
152                 /// </summary>\r
153                 /// <exception name="System.IO.IOException">\r
154                 /// if a i/o error occured.\r
155                 /// </exception>\r
156                 /// <exception name="System.IO.EndOfStreamException">\r
157                 /// if the file ends prematurely\r
158                 /// </exception>\r
159                 int ReadLeInt()\r
160                 {\r
161                         return ReadLeShort() | ReadLeShort() << 16;\r
162                 }\r
163                 \r
164                 /// <summary>\r
165                 /// Read the central directory of a zip file and fill the entries\r
166                 /// array.  This is called exactly once by the constructors.\r
167                 /// </summary>\r
168                 /// <exception name="System.IO.IOException">\r
169                 /// if a i/o error occured.\r
170                 /// </exception>\r
171                 /// <exception name="ICSharpCode.SharpZipLib.ZipException">\r
172                 /// if the central directory is malformed\r
173                 /// </exception>\r
174                 void ReadEntries()\r
175                 {\r
176                         /* Search for the End Of Central Directory.  When a zip comment is\r
177                         * present the directory may start earlier.\r
178                         * FIXME: This searches the whole file in a very slow manner if the\r
179                         * file isn't a zip file.\r
180                         */\r
181                         long pos = baseStream.Length - ZipConstants.ENDHDR;\r
182                         do {\r
183                                 if (pos < 0) {\r
184                                         throw new ZipException("central directory not found, probably not a zip file");\r
185                                 }\r
186                                 baseStream.Seek(pos--, SeekOrigin.Begin);\r
187                         } while (ReadLeInt() != ZipConstants.ENDSIG);\r
188                         \r
189                         long oldPos = baseStream.Position;\r
190                         baseStream.Position += ZipConstants.ENDTOT - ZipConstants.ENDNRD;\r
191                         \r
192                         if (baseStream.Position - oldPos != ZipConstants.ENDTOT - ZipConstants.ENDNRD) {\r
193                                 throw new EndOfStreamException();\r
194                         }\r
195                         int count = ReadLeShort();\r
196                         \r
197                         oldPos = baseStream.Position;\r
198                         baseStream.Position += ZipConstants.ENDOFF - ZipConstants.ENDSIZ;\r
199                         \r
200                         if (baseStream.Position - oldPos != ZipConstants.ENDOFF - ZipConstants.ENDSIZ) {\r
201                                 throw new EndOfStreamException();\r
202                         }\r
203                         \r
204                         int centralOffset = ReadLeInt();\r
205                         \r
206                         entries = new ZipEntry[count];\r
207                         baseStream.Seek(centralOffset, SeekOrigin.Begin);\r
208                         for (int i = 0; i < count; i++) {\r
209                                 if (ReadLeInt() != ZipConstants.CENSIG) {\r
210                                         throw new ZipException("Wrong Central Directory signature");\r
211                                 }\r
212                                 \r
213                                 oldPos = baseStream.Position;\r
214                                 baseStream.Position += ZipConstants.CENHOW - ZipConstants.CENVEM;\r
215                                 \r
216                                 if (baseStream.Position - oldPos != ZipConstants.CENHOW - ZipConstants.CENVEM) {\r
217                                         throw new EndOfStreamException();\r
218                                 }\r
219                                 int method = ReadLeShort();\r
220                                 int dostime = ReadLeInt();\r
221                                 int crc = ReadLeInt();\r
222                                 int csize = ReadLeInt();\r
223                                 int size = ReadLeInt();\r
224                                 int nameLen = ReadLeShort();\r
225                                 int extraLen = ReadLeShort();\r
226                                 int commentLen = ReadLeShort();\r
227                                 \r
228                                 oldPos = baseStream.Position;\r
229                                 baseStream.Position += ZipConstants.CENOFF - ZipConstants.CENDSK;\r
230                                 if (baseStream.Position - oldPos != ZipConstants.CENOFF - ZipConstants.CENDSK) {\r
231                                         throw new EndOfStreamException();\r
232                                 }\r
233                                 int offset = ReadLeInt();\r
234                                 \r
235                                 byte[] buffer = new byte[Math.Max(nameLen, commentLen)];\r
236                                 \r
237                                 baseStream.Read(buffer, 0, nameLen);\r
238                                 string name = ZipConstants.ConvertToString(buffer);\r
239                                 \r
240                                 ZipEntry entry = new ZipEntry(name);\r
241                                 entry.CompressionMethod = (CompressionMethod)method;\r
242                                 entry.Crc = crc & 0xffffffffL;\r
243                                 entry.Size = size & 0xffffffffL;\r
244                                 entry.CompressedSize = csize & 0xffffffffL;\r
245                                 entry.DosTime = dostime;\r
246                                 if (extraLen > 0) {\r
247                                         byte[] extra = new byte[extraLen];\r
248                                         baseStream.Read(extra, 0, extraLen);\r
249                                         entry.ExtraData = extra;\r
250                                 }\r
251                                 if (commentLen > 0) {\r
252                                         baseStream.Read(buffer, 0, commentLen);\r
253                                         entry.Comment = ZipConstants.ConvertToString(buffer);\r
254                                 }\r
255                                 entry.zipFileIndex = i;\r
256                                 entry.offset = offset;\r
257                                 entries[i] = entry;\r
258                         }\r
259                 }\r
260                 \r
261                 /// <summary>\r
262                 /// Closes the ZipFile.  This also closes all input streams given by\r
263                 /// this class.  After this is called, no further method should be\r
264                 /// called.\r
265                 /// </summary>\r
266                 /// <exception name="System.IO.IOException">\r
267                 /// if a i/o error occured.\r
268                 /// </exception>\r
269                 public void Close()\r
270                 {\r
271                         entries = null;\r
272                         lock(baseStream) {\r
273                                 baseStream.Close();\r
274                         }\r
275                 }\r
276                 \r
277                 /// <summary>\r
278                 /// Returns an IEnumerator of all Zip entries in this Zip file.\r
279                 /// </summary>\r
280                 public IEnumerator GetEnumerator()\r
281                 {\r
282                         if (entries == null) {\r
283                                 throw new InvalidOperationException("ZipFile has closed");\r
284                         }\r
285                         \r
286                         return new ZipEntryEnumeration(entries);\r
287                 }\r
288                 \r
289                 int GetEntryIndex(string name)\r
290                 {\r
291                         for (int i = 0; i < entries.Length; i++) {\r
292                                 if (name.Equals(entries[i].Name)) {\r
293                                         return i;\r
294                                 }\r
295                         }\r
296                         return -1;\r
297                 }\r
298                 \r
299                 /// <summary>\r
300                 /// Searches for a zip entry in this archive with the given name.\r
301                 /// </summary>\r
302                 /// <param name="name">\r
303                 /// the name. May contain directory components separated by slashes ('/').\r
304                 /// </param>\r
305                 /// <returns>\r
306                 /// the zip entry, or null if no entry with that name exists.\r
307                 /// </returns>\r
308                 public ZipEntry GetEntry(string name)\r
309                 {\r
310                         if (entries == null) {\r
311                                 throw new InvalidOperationException("ZipFile has closed");\r
312                         }\r
313                         int index = GetEntryIndex(name);\r
314                         return index >= 0 ? (ZipEntry) entries[index].Clone() : null;\r
315                 }\r
316                 \r
317                 /// <summary>\r
318                 /// Checks, if the local header of the entry at index i matches the\r
319                 /// central directory, and returns the offset to the data.\r
320                 /// </summary>\r
321                 /// <returns>\r
322                 /// the start offset of the (compressed) data.\r
323                 /// </returns>\r
324                 /// <exception name="System.IO.IOException">\r
325                 /// if a i/o error occured.\r
326                 /// </exception>\r
327                 /// <exception name="ICSharpCode.SharpZipLib.ZipException">\r
328                 /// if the local header doesn't match the central directory header\r
329                 /// </exception>\r
330                 long CheckLocalHeader(ZipEntry entry)\r
331                 {\r
332                         lock(baseStream) {\r
333                                 baseStream.Seek(entry.offset, SeekOrigin.Begin);\r
334                                 if (ReadLeInt() != ZipConstants.LOCSIG) {\r
335                                         throw new ZipException("Wrong Local header signature");\r
336                                 }\r
337                                 \r
338                                 /* skip version and flags */\r
339                                 long oldPos = baseStream.Position;\r
340                                 baseStream.Position += ZipConstants.LOCHOW - ZipConstants.LOCVER;\r
341                                 if (baseStream.Position - oldPos != ZipConstants.LOCHOW - ZipConstants.LOCVER) {\r
342                                         throw new EndOfStreamException();\r
343                                 }\r
344                                 \r
345                                 if (entry.CompressionMethod != (CompressionMethod)ReadLeShort()) {\r
346                                         throw new ZipException("Compression method mismatch");\r
347                                 }\r
348                                 \r
349                                 /* Skip time, crc, size and csize */\r
350                                 oldPos = baseStream.Position;\r
351                                 baseStream.Position += ZipConstants.LOCNAM - ZipConstants.LOCTIM;\r
352                                 \r
353                                 if (baseStream.Position - oldPos != ZipConstants.LOCNAM - ZipConstants.LOCTIM) {\r
354                                         throw new EndOfStreamException();\r
355                                 }\r
356                                 \r
357                                 if (entry.Name.Length != ReadLeShort()) {\r
358                                         throw new ZipException("file name length mismatch");\r
359                                 }\r
360                                 \r
361                                 int extraLen = entry.Name.Length + ReadLeShort();\r
362                                 return entry.offset + ZipConstants.LOCHDR + extraLen;\r
363                         }\r
364                 }\r
365                 \r
366                 /// <summary>\r
367                 /// Creates an input stream reading the given zip entry as\r
368                 /// uncompressed data.  Normally zip entry should be an entry\r
369                 /// returned by GetEntry().\r
370                 /// </summary>\r
371                 /// <returns>\r
372                 /// the input stream.\r
373                 /// </returns>\r
374                 /// <exception name="System.IO.IOException">\r
375                 /// if a i/o error occured.\r
376                 /// </exception>\r
377                 /// <exception name="ICSharpCode.SharpZipLib.ZipException">\r
378                 /// if the Zip archive is malformed.\r
379                 /// </exception>\r
380                 public Stream GetInputStream(ZipEntry entry)\r
381                 {\r
382                         if (entries == null) {\r
383                                 throw new InvalidOperationException("ZipFile has closed");\r
384                         }\r
385                         \r
386                         int index = entry.zipFileIndex;\r
387                         if (index < 0 || index >= entries.Length || entries[index].Name != entry.Name) {\r
388                                 index = GetEntryIndex(entry.Name);\r
389                                 if (index < 0) {\r
390                                         throw new IndexOutOfRangeException();\r
391                                 }\r
392                         }\r
393                         \r
394                         long start = CheckLocalHeader(entries[index]);\r
395                         CompressionMethod method = entries[index].CompressionMethod;\r
396                         Stream istr = new PartialInputStream(baseStream, start, entries[index].CompressedSize);\r
397                         switch (method) {\r
398                                 case CompressionMethod.Stored:\r
399                                         return istr;\r
400                                 case CompressionMethod.Deflated:\r
401                                         return new InflaterInputStream(istr, new Inflater(true));\r
402                                 default:\r
403                                         throw new ZipException("Unknown compression method " + method);\r
404                         }\r
405                 }\r
406                 \r
407                 /// <summary>\r
408                 /// Returns the name of this zip file.\r
409                 /// </summary>\r
410                 public string Name {\r
411                         get {\r
412                                 return name;\r
413                         }\r
414                 }\r
415                 \r
416                 /// <summary>\r
417                 /// Returns the number of entries in this zip file.\r
418                 /// </summary>\r
419                 public int Size {\r
420                         get {\r
421                                 try {\r
422                                         return entries.Length;\r
423                                 } catch (Exception) {\r
424                                         throw new InvalidOperationException("ZipFile has closed");\r
425                                 }\r
426                         }\r
427                 }\r
428                 \r
429                 class ZipEntryEnumeration : IEnumerator\r
430                 {\r
431                         ZipEntry[] array;\r
432                         int ptr = -1;\r
433                         \r
434                         public ZipEntryEnumeration(ZipEntry[] arr)\r
435                         {\r
436                                 array = arr;\r
437                         }\r
438                         \r
439                         public object Current {\r
440                                 get {\r
441                                         return array[ptr];\r
442                                 }\r
443                         }\r
444                         \r
445                         public void Reset()\r
446                         {\r
447                                 ptr = -1;\r
448                         }\r
449                         \r
450                         public bool MoveNext() \r
451                         {\r
452                                 return (++ptr < array.Length);\r
453                         }\r
454                 }\r
455                 \r
456                 class PartialInputStream : InflaterInputStream\r
457                 {\r
458                         Stream baseStream;\r
459                         long filepos, end;\r
460                         \r
461                         public PartialInputStream(Stream baseStream, long start, long len) : base(baseStream)\r
462                         {\r
463                                 this.baseStream = baseStream;\r
464                                 filepos = start;\r
465                                 end = start + len;\r
466                         }\r
467                         \r
468                         public override int Available {\r
469                                 get {\r
470                                         long amount = end - filepos;\r
471                                         if (amount > Int32.MaxValue) {\r
472                                                 return Int32.MaxValue;\r
473                                         }\r
474                                         \r
475                                         return (int) amount;\r
476                                 }\r
477                         }\r
478                         \r
479                         public override int ReadByte()\r
480                         {\r
481                                 if (filepos == end) {\r
482                                         return -1;\r
483                                 }\r
484                                 lock(baseStream) {\r
485                                         baseStream.Seek(filepos++, SeekOrigin.Begin);\r
486                                         return baseStream.ReadByte();\r
487                                 }\r
488                         }\r
489                         \r
490                         public override int Read(byte[] b, int off, int len)\r
491                         {\r
492                                 if (len > end - filepos) {\r
493                                         len = (int) (end - filepos);\r
494                                         if (len == 0) {\r
495                                                 return -1;\r
496                                         }\r
497                                 }\r
498                                 lock(baseStream) {\r
499                                         baseStream.Seek(filepos, SeekOrigin.Begin);\r
500                                         int count = baseStream.Read(b, off, len);\r
501                                         if (count > 0) {\r
502                                                 filepos += len;\r
503                                         }\r
504                                         return count;\r
505                                 }\r
506                         }\r
507                         \r
508                         public long SkipBytes(long amount)\r
509                         {\r
510                                 if (amount < 0) {\r
511                                         throw new ArgumentOutOfRangeException();\r
512                                 }\r
513                                 if (amount > end - filepos) {\r
514                                         amount = end - filepos;\r
515                                 }\r
516                                 filepos += amount;\r
517                                 return amount;\r
518                         }\r
519                 }\r
520         }\r
521 }\r