2005-01-31 Zoltan Varga <vargaz@freemail.hu>
[mono.git] / mcs / class / ICSharpCode.SharpZipLib / ICSharpCode.SharpZipLib / Zip / ZipInputStream.cs
1 // ZipInputStream.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.Text;\r
40 using System.IO;\r
41 \r
42 using ICSharpCode.SharpZipLib.Checksums;\r
43 using ICSharpCode.SharpZipLib.Zip.Compression;\r
44 using ICSharpCode.SharpZipLib.Zip.Compression.Streams;\r
45 \r
46 namespace ICSharpCode.SharpZipLib.Zip \r
47 {\r
48         /// <summary>\r
49         /// This is a FilterInputStream that reads the files baseInputStream an zip archive\r
50         /// one after another.  It has a special method to get the zip entry of\r
51         /// the next file.  The zip entry contains information about the file name\r
52         /// size, compressed size, CRC, etc.\r
53         /// It includes support for STORED and DEFLATED entries.\r
54         /// \r
55         /// author of the original java version : Jochen Hoenicke\r
56         /// </summary>\r
57         /// <example> This sample shows how to read a zip file\r
58         /// <code lang="C#">\r
59         /// using System;\r
60         /// using System.Text;\r
61         /// using System.IO;\r
62         /// \r
63         /// using NZlib.Zip;\r
64         /// \r
65         /// class MainClass\r
66         /// {\r
67         ///     public static void Main(string[] args)\r
68         ///     {\r
69         ///             ZipInputStream s = new ZipInputStream(File.OpenRead(args[0]));\r
70         ///             \r
71         ///             ZipEntry theEntry;\r
72         ///             while ((theEntry = s.GetNextEntry()) != null) {\r
73         ///                     int size = 2048;\r
74         ///                     byte[] data = new byte[2048];\r
75         ///                     \r
76         ///                     Console.Write("Show contents (y/n) ?");\r
77         ///                     if (Console.ReadLine() == "y") {\r
78         ///                             while (true) {\r
79         ///                                     size = s.Read(data, 0, data.Length);\r
80         ///                                     if (size > 0) {\r
81         ///                                             Console.Write(new ASCIIEncoding().GetString(data, 0, size));\r
82         ///                                     } else {\r
83         ///                                             break;\r
84         ///                                     }\r
85         ///                             }\r
86         ///                     }\r
87         ///             }\r
88         ///             s.Close();\r
89         ///     }\r
90         /// }   \r
91         /// </code>\r
92         /// </example>\r
93         public class ZipInputStream : InflaterInputStream\r
94         {\r
95                 Crc32 crc = new Crc32();\r
96                 ZipEntry entry = null;\r
97                 \r
98                 long size;\r
99                 int method;\r
100                 int flags;\r
101                 long avail;\r
102                 string password = null;\r
103                 \r
104                 public string Password {\r
105                         get {\r
106                                 return password;\r
107                         }\r
108                         set {\r
109                                 password = value;\r
110                         }\r
111                 }\r
112                 \r
113                 /// <summary>\r
114                 /// Creates a new Zip input stream, reading a zip archive.\r
115                 /// </summary>\r
116                 public ZipInputStream(Stream baseInputStream) : base(baseInputStream, new Inflater(true))\r
117                 {\r
118                 }\r
119                 \r
120                 void FillBuf()\r
121                 {\r
122                         avail = len = baseInputStream.Read(buf, 0, buf.Length);\r
123                 }\r
124                 \r
125                 int ReadBuf(byte[] outBuf, int offset, int length)\r
126                 {\r
127                         if (avail <= 0) {\r
128                                 FillBuf();\r
129                                 if (avail <= 0) {\r
130                                         return 0;\r
131                                 }\r
132                         }\r
133                         if (length > avail) {\r
134                                 length = (int)avail;\r
135                         }\r
136                         System.Array.Copy(buf, len - (int)avail, outBuf, offset, length);\r
137                         avail -= length;\r
138                         return length;\r
139                 }\r
140                 \r
141                 void ReadFully(byte[] outBuf)\r
142                 {\r
143                         int off = 0;\r
144                         int len = outBuf.Length;\r
145                         while (len > 0) {\r
146                                 int count = ReadBuf(outBuf, off, len);\r
147                                 if (count == -1) {\r
148                                         throw new Exception(); \r
149                                 }\r
150                                 off += count;\r
151                                 len -= count;\r
152                         }\r
153                 }\r
154                 \r
155                 int ReadLeByte()\r
156                 {\r
157                         if (avail <= 0) {\r
158                                 FillBuf();\r
159                                 if (avail <= 0) {\r
160                                         throw new ZipException("EOF in header");\r
161                                 }\r
162                         }\r
163                         return buf[len - avail--] & 0xff;\r
164                 }\r
165                 \r
166                 /// <summary>\r
167                 /// Read an unsigned short baseInputStream little endian byte order.\r
168                 /// </summary>\r
169                 int ReadLeShort()\r
170                 {\r
171                         return ReadLeByte() | (ReadLeByte() << 8);\r
172                 }\r
173                 \r
174                 /// <summary>\r
175                 /// Read an int baseInputStream little endian byte order.\r
176                 /// </summary>\r
177                 int ReadLeInt()\r
178                 {\r
179                         return ReadLeShort() | (ReadLeShort() << 16);\r
180                 }\r
181                 \r
182                 /// <summary>\r
183                 /// Read an int baseInputStream little endian byte order.\r
184                 /// </summary>\r
185                 long ReadLeLong()\r
186                 {\r
187                         return ReadLeInt() | (ReadLeInt() << 32);\r
188                 }\r
189                 \r
190                 /// <summary>\r
191                 /// Open the next entry from the zip archive, and return its description.\r
192                 /// If the previous entry wasn't closed, this method will close it.\r
193                 /// </summary>\r
194                 public ZipEntry GetNextEntry()\r
195                 {\r
196                         if (crc == null) {\r
197                                 throw new InvalidOperationException("Closed.");\r
198                         }\r
199                         if (entry != null) {\r
200                                 CloseEntry();\r
201                         }\r
202                         \r
203                         if (this.cryptbuffer != null) {\r
204                                 if (avail == 0 && inf.RemainingInput != 0) {\r
205                                         avail = inf.RemainingInput - 16;\r
206                                         inf.Reset();\r
207                                 }\r
208                                 baseInputStream.Position -= this.len;\r
209                                 baseInputStream.Read(this.buf, 0, this.len);\r
210                         }\r
211                         \r
212                         int header = ReadLeInt();\r
213                         \r
214                         // -jr- added end sig for empty zip files, Zip64 end sig and digital sig for files that have them...\r
215                         if (header == ZipConstants.CENSIG || \r
216                             header == ZipConstants.ENDSIG || \r
217                             header == ZipConstants.CENDIGITALSIG || \r
218                             header == ZipConstants.CENSIG64) {\r
219                                 // Central Header reached or end of empty zip file\r
220                                 Close();\r
221                                 return null;\r
222                         }\r
223                         // -jr- 07-Dec-2003 ignore spanning temporary signatures if found\r
224                         // SPANNINGSIG is same as descriptor signature and is untested as yet.\r
225                         if (header == ZipConstants.SPANTEMPSIG || header == ZipConstants.SPANNINGSIG) {\r
226                                 header = ReadLeInt();\r
227                         }\r
228                         \r
229                         if (header != ZipConstants.LOCSIG) {\r
230                                 throw new ZipException("Wrong Local header signature: 0x" + String.Format("{0:X}", header));\r
231                         }\r
232                         \r
233                         short version = (short)ReadLeShort();\r
234                         \r
235                         flags = ReadLeShort();\r
236                         method = ReadLeShort();\r
237                         uint dostime = (uint)ReadLeInt();\r
238                         int crc2 = ReadLeInt();\r
239                         csize = ReadLeInt();\r
240                         size = ReadLeInt();\r
241                         int nameLen = ReadLeShort();\r
242                         int extraLen = ReadLeShort();\r
243                         bool isCrypted = (flags & 1) == 1;\r
244                         if (method == ZipOutputStream.STORED && (!isCrypted && csize != size || (isCrypted && csize - 12 != size))) {\r
245                                 throw new ZipException("Stored, but compressed != uncompressed");\r
246                         }\r
247                         \r
248                         byte[] buffer = new byte[nameLen];\r
249                         ReadFully(buffer);\r
250                         \r
251                         string name = ZipConstants.ConvertToString(buffer);\r
252                         \r
253                         entry = new ZipEntry(name);\r
254                         entry.IsCrypted = isCrypted;\r
255                         entry.Version = (ushort)version;\r
256                         if (method != 0 && method != 8) {\r
257                                 throw new ZipException("unknown compression method " + method);\r
258                         }\r
259                         entry.CompressionMethod = (CompressionMethod)method;\r
260                         \r
261                         if ((flags & 8) == 0) {\r
262                                 entry.Crc  = crc2 & 0xFFFFFFFFL;\r
263                                 entry.Size = size & 0xFFFFFFFFL;\r
264                                 entry.CompressedSize = csize & 0xFFFFFFFFL;\r
265                         }\r
266                         \r
267                         entry.DosTime = dostime;\r
268                         \r
269                         if (extraLen > 0) {\r
270                                 byte[] extra = new byte[extraLen];\r
271                                 ReadFully(extra);\r
272                                 entry.ExtraData = extra;\r
273                         }\r
274                         \r
275                         // test for encryption\r
276                         if (isCrypted) {\r
277                                 if (password == null) {\r
278                                         throw new ZipException("No password set.");\r
279                                 }\r
280                                 InitializePassword(password);\r
281                                 cryptbuffer = new byte[12];\r
282                                 ReadFully(cryptbuffer);\r
283                                 DecryptBlock(cryptbuffer, 0, cryptbuffer.Length);\r
284                                 if ((flags & 8) == 0) {// -jr- 10-Feb-2004 Dont yet know correct size here....\r
285                                         csize -= 12;\r
286                                 }\r
287                         } else {\r
288                                 cryptbuffer = null;\r
289                         }\r
290                         \r
291                         if (method == ZipOutputStream.DEFLATED && avail > 0) {\r
292                                 System.Array.Copy(buf, len - (int)avail, buf, 0, (int)avail);\r
293                                 len = (int)avail;\r
294                                 avail = 0;\r
295                                 if (isCrypted) {\r
296                                         DecryptBlock(buf, 0, Math.Min((int)csize, len));\r
297                                 }\r
298                                 inf.SetInput(buf, 0, len);\r
299                         }\r
300                         \r
301                         return entry;\r
302                 }\r
303                 private void ReadDataDescr()\r
304                 {\r
305                         if (ReadLeInt() != ZipConstants.EXTSIG) {\r
306                                 throw new ZipException("Data descriptor signature not found");\r
307                         }\r
308                         entry.Crc = ReadLeInt() & 0xFFFFFFFFL;\r
309                         csize = ReadLeInt();\r
310                         size = ReadLeInt();\r
311                         entry.Size = size & 0xFFFFFFFFL;\r
312                         entry.CompressedSize = csize & 0xFFFFFFFFL;\r
313                 }\r
314                 \r
315                 /// <summary>\r
316                 /// Closes the current zip entry and moves to the next one.\r
317                 /// </summary>\r
318                 public void CloseEntry()\r
319                 {\r
320                         if (crc == null) {\r
321                                 throw new InvalidOperationException("Closed.");\r
322                         }\r
323                         \r
324                         if (entry == null) {\r
325                                 return;\r
326                         }\r
327                         \r
328                         if (method == ZipOutputStream.DEFLATED) {\r
329                                 if ((flags & 8) != 0) {\r
330                                         /* We don't know how much we must skip, read until end. */\r
331                                         byte[] tmp = new byte[2048];\r
332                                         while (Read(tmp, 0, tmp.Length) > 0)\r
333                                                 ;\r
334                                         /* read will close this entry */\r
335                                         return;\r
336                                 }\r
337                                 csize -= inf.TotalIn;\r
338                                 avail = inf.RemainingInput;\r
339                         }\r
340                         if (avail > csize && csize >= 0) {\r
341                                 avail -= csize;\r
342                         } else {\r
343                                 csize -= avail;\r
344                                 avail = 0;\r
345                                 while (csize != 0) {\r
346                                         int skipped = (int)base.Skip(csize & 0xFFFFFFFFL);\r
347                                         \r
348                                         if (skipped <= 0) {\r
349                                                 throw new ZipException("zip archive ends early.");\r
350                                         }\r
351                                         \r
352                                         csize -= skipped;\r
353                                 }\r
354                         }\r
355                         \r
356                         size = 0;\r
357                         crc.Reset();\r
358                         if (method == ZipOutputStream.DEFLATED) {\r
359                                 inf.Reset();\r
360                         }\r
361                         entry = null;\r
362                 }\r
363                 \r
364                 public override int Available {\r
365                         get {\r
366                                 return entry != null ? 1 : 0;\r
367                         }\r
368                 }\r
369                 \r
370                 /// <summary>\r
371                 /// Reads a byte from the current zip entry.\r
372                 /// </summary>\r
373                 /// <returns>\r
374                 /// the byte or -1 on EOF.\r
375                 /// </returns>\r
376                 /// <exception name="System.IO.IOException">\r
377                 /// IOException if a i/o error occured.\r
378                 /// </exception>\r
379                 /// <exception name="ICSharpCode.SharpZipLib.ZipException">\r
380                 /// ZipException if the deflated stream is corrupted.\r
381                 /// </exception>\r
382                 public override int ReadByte()\r
383                 {\r
384                         byte[] b = new byte[1];\r
385                         if (Read(b, 0, 1) <= 0) {\r
386                                 return -1; // ok\r
387                         }\r
388                         return b[0] & 0xff;\r
389                 }\r
390                 \r
391                 /// <summary>\r
392                 /// Reads a block of bytes from the current zip entry.\r
393                 /// </summary>\r
394                 /// <returns>\r
395                 /// the number of bytes read (may be smaller, even before EOF), or -1 on EOF.\r
396                 /// </returns>\r
397                 /// <exception name="Exception">\r
398                 /// IOException if a i/o error occured.\r
399                 /// ZipException if the deflated stream is corrupted.\r
400                 /// </exception>\r
401                 public override int Read(byte[] b, int off, int len)\r
402                 {\r
403                         if (crc == null) {\r
404                                 throw new InvalidOperationException("Closed.");\r
405                         }\r
406                         \r
407                         if (entry == null) {\r
408                                 return 0;\r
409                         }\r
410                         bool finished = false;\r
411                         \r
412                         switch (method) {\r
413                                 case ZipOutputStream.DEFLATED:\r
414                                         len = base.Read(b, off, len);\r
415                                         if (len <= 0) { // TODO BUG1 -jr- Check this was < 0 but avail was not adjusted causing failure in later calls\r
416                                                 if (!inf.IsFinished) {\r
417                                                         throw new ZipException("Inflater not finished!?");\r
418                                                 }\r
419                                                 avail = inf.RemainingInput;\r
420                                                 \r
421                                                 // BUG1 -jr- With bit 3 set you dont yet know the size\r
422                                                 if ((flags & 8) == 0 && (inf.TotalIn != csize || inf.TotalOut != size)) {\r
423                                                         throw new ZipException("size mismatch: " + csize + ";" + size + " <-> " + inf.TotalIn + ";" + inf.TotalOut);\r
424                                                 }\r
425                                                 inf.Reset();\r
426                                                 finished = true;\r
427                                         }\r
428                                         break;\r
429                                 \r
430                                 case ZipOutputStream.STORED:\r
431                                         if (len > csize && csize >= 0) {\r
432                                                 len = (int)csize;\r
433                                         }\r
434                                         len = ReadBuf(b, off, len);\r
435                                         if (len > 0) {\r
436                                                 csize -= len;\r
437                                                 size -= len;\r
438                                         }\r
439                                         \r
440                                         if (csize == 0) {\r
441                                                 finished = true;\r
442                                         } else {\r
443                                                 if (len < 0) {\r
444                                                         throw new ZipException("EOF in stored block");\r
445                                                 }\r
446                                         }\r
447                                         \r
448                                         // decrypting crypted data\r
449                                         if (cryptbuffer != null) {\r
450                                                 DecryptBlock(b, off, len);\r
451                                         }\r
452                                         \r
453                                         break;\r
454                         }\r
455                                 \r
456                         if (len > 0) {\r
457                                 crc.Update(b, off, len);\r
458                         }\r
459                         \r
460                         if (finished) {\r
461                                 if ((flags & 8) != 0) {\r
462                                         ReadDataDescr();\r
463                                 }\r
464                                 \r
465                                 if ((crc.Value & 0xFFFFFFFFL) != entry.Crc && entry.Crc != -1) {\r
466                                         throw new ZipException("CRC mismatch");\r
467                                 }\r
468                                 crc.Reset();\r
469                                 entry = null;\r
470                         }\r
471                         return len;\r
472                 }\r
473                 \r
474                 /// <summary>\r
475                 /// Closes the zip file.\r
476                 /// </summary>\r
477                 /// <exception name="Exception">\r
478                 /// if a i/o error occured.\r
479                 /// </exception>\r
480                 public override void Close()\r
481                 {\r
482                         base.Close();\r
483                         crc = null;\r
484                         entry = null;\r
485                 }\r
486                 \r
487         }\r
488 }\r