1:   // ZipFile.cs
2:   // Copyright (C) 2001 Mike Krueger
3:   //
4:   // This file was translated from java, it was part of the GNU Classpath
5:   // Copyright (C) 2001 Free Software Foundation, Inc.
6:   //
7:   // This program is free software; you can redistribute it and/or
8:   // modify it under the terms of the GNU General Public License
9:   // as published by the Free Software Foundation; either version 2
10:   // of the License, or (at your option) any later version.
11:   //
12:   // This program is distributed in the hope that it will be useful,
13:   // but WITHOUT ANY WARRANTY; without even the implied warranty of
14:   // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15:   // GNU General Public License for more details.
16:   //
17:   // You should have received a copy of the GNU General Public License
18:   // along with this program; if not, write to the Free Software
19:   // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
20:   //
21:   // Linking this library statically or dynamically with other modules is
22:   // making a combined work based on this library.  Thus, the terms and
23:   // conditions of the GNU General Public License cover the whole
24:   // combination.
25:   // 
26:   // As a special exception, the copyright holders of this library give you
27:   // permission to link this library with independent modules to produce an
28:   // executable, regardless of the license terms of these independent
29:   // modules, and to copy and distribute the resulting executable under
30:   // terms of your choice, provided that you also meet, for each linked
31:   // independent module, the terms and conditions of the license of that
32:   // module.  An independent module is a module which is not derived from
33:   // or based on this library.  If you modify this library, you may extend
34:   // this exception to your version of the library, but you are not
35:   // obligated to do so.  If you do not wish to do so, delete this
36:   // exception statement from your version.
37:  
38:   using System;
39:   using System.Collections;
40:   using System.IO;
41:   using System.Text;
42:  
43:   using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
44:   using ICSharpCode.SharpZipLib.Zip.Compression;
45:  
46:   namespace ICSharpCode.SharpZipLib.Zip {
47:       
48:       /// <summary>
49:       /// This class represents a Zip archive.  You can ask for the contained
50:       /// entries, or get an input stream for a file entry.  The entry is
51:       /// automatically decompressed.
52:       /// 
53:       /// This class is thread safe:  You can open input streams for arbitrary
54:       /// entries in different threads.
55:       /// 
56:       /// author of the original java version : Jochen Hoenicke
57:       /// </summary>
58:       /// <example>
59:       /// using System;
60:       /// using System.Text;
61:       /// using System.Collections;
62:       /// using System.IO;
63:       /// 
64:       /// using NZlib.Zip;
65:       /// 
66:       /// class MainClass
67:       /// {
68:       ///     static public void Main(string[] args)
69:       ///     {
70:       ///         ZipFile zFile = new ZipFile(args[0]);
71:       ///         Console.WriteLine("Listing of : " + zFile.Name);
72:       ///         Console.WriteLine("");
73:       ///         Console.WriteLine("Raw Size    Size      Date     Time     Name");
74:       ///         Console.WriteLine("--------  --------  --------  ------  ---------");
75:       ///         foreach (ZipEntry e in zFile) {
76:       ///             DateTime d = e.DateTime;
77:       ///             Console.WriteLine("{0, -10}{1, -10}{2}  {3}   {4}", e.Size, e.CompressedSize,
78:       ///                                                                 d.ToString("dd-MM-yy"), d.ToString("t"),
79:       ///                                                                 e.Name);
80:       ///         }
81:       ///     }
82:       /// }
83:       /// </example>
84:       public class ZipFile : IEnumerable
85:       {
86:           string     name;
87:           Stream     baseStream;
88:           ZipEntry[] entries;
89:           
90:           /// <summary>
91:           /// Opens a Zip file with the given name for reading.
92:           /// </summary>
93:           /// <exception name="System.IO.IOException">
94:           /// IOException if a i/o error occured.
95:           /// </exception>
96:           /// <exception name="ICSharpCode.SharpZipLib.ZipException">
97:           /// if the file doesn't contain a valid zip archive.
98:           /// </exception>
99:           public ZipFile(string namethis(File.OpenRead(name))
100:           {
101:           }
102:           
103:           /// <summary>
104:           /// Opens a Zip file reading the given FileStream
105:           /// </summary>
106:           /// <exception name="System.IO.IOException">
107:           /// IOException if a i/o error occured.
108:           /// </exception>
109:           /// <exception name="ICSharpCode.SharpZipLib.ZipException">
110:           /// if the file doesn't contain a valid zip archive.
111:           /// </exception>
112:           public ZipFile(FileStream file)
113:           {
114:               this.baseStream  file;
115:               this.name file.Name;
116:               ReadEntries();
117:           }
118:           
119:           /// <summary>
120:           /// Opens a Zip file reading the given Stream
121:           /// </summary>
122:           /// <exception name="System.IO.IOException">
123:           /// IOException if a i/o error occured.
124:           /// </exception>
125:           /// <exception name="ICSharpCode.SharpZipLib.ZipException">
126:           /// if the file doesn't contain a valid zip archive.
127:           /// </exception>
128:           public ZipFile(Stream baseStream)
129:           {
130:               this.baseStream  baseStream;
131:               this.name null;
132:               ReadEntries();
133:           }
134:           
135:           
136:           /// <summary>
137:           /// Read an unsigned short in little endian byte order.
138:           /// </summary>
139:           /// <exception name="System.IO.IOException">
140:           /// if a i/o error occured.
141:           /// </exception>
142:           /// <exception name="System.IO.EndOfStreamException">
143:           /// if the file ends prematurely
144:           /// </exception>
145:           int ReadLeShort()
146:           {
147:               return baseStream.ReadByte() | baseStream.ReadByte() << 8;
148:           }
149:           
150:           /// <summary>
151:           /// Read an int in little endian byte order.
152:           /// </summary>
153:           /// <exception name="System.IO.IOException">
154:           /// if a i/o error occured.
155:           /// </exception>
156:           /// <exception name="System.IO.EndOfStreamException">
157:           /// if the file ends prematurely
158:           /// </exception>
159:           int ReadLeInt()
160:           {
161:               return ReadLeShort() | ReadLeShort() << 16;
162:           }
163:           
164:           /// <summary>
165:           /// Read the central directory of a zip file and fill the entries
166:           /// array.  This is called exactly once by the constructors.
167:           /// </summary>
168:           /// <exception name="System.IO.IOException">
169:           /// if a i/o error occured.
170:           /// </exception>
171:           /// <exception name="ICSharpCode.SharpZipLib.ZipException">
172:           /// if the central directory is malformed
173:           /// </exception>
174:           void ReadEntries()
175:           {
176:               /* Search for the End Of Central Directory.  When a zip comment is
177:               * present the directory may start earlier.
178:               FIXME: This searches the whole file in a very slow manner if the
179:               * file isn't a zip file.
180:               */
181:               long pos baseStream.Length ZipConstants.ENDHDR;
182:               do {
183:                   if (pos 0) {
184:                       throw new ZipException("central directory not found, probably not a zip file");
185:                   }
186:                   baseStream.Seek(pos--, SeekOrigin.Begin);
187:               while (ReadLeInt() != ZipConstants.ENDSIG);
188:               
189:               long oldPos baseStream.Position;
190:               baseStream.Position += ZipConstants.ENDTOT ZipConstants.ENDNRD;
191:               
192:               if (baseStream.Position oldPos != ZipConstants.ENDTOT ZipConstants.ENDNRD) {
193:                   throw new EndOfStreamException();
194:               }
195:               int count ReadLeShort();
196:               
197:               oldPos baseStream.Position;
198:               baseStream.Position += ZipConstants.ENDOFF ZipConstants.ENDSIZ;
199:               
200:               if (baseStream.Position oldPos != ZipConstants.ENDOFF ZipConstants.ENDSIZ) {
201:                   throw new EndOfStreamException();
202:               }
203:               
204:               int centralOffset ReadLeInt();
205:               
206:               entries new ZipEntry[count];
207:               baseStream.Seek(centralOffsetSeekOrigin.Begin);
208:               for (int 0counti++) {
209:                   if (ReadLeInt() != ZipConstants.CENSIG) {
210:                       throw new ZipException("Wrong Central Directory signature");
211:                   }
212:                   
213:                   oldPos baseStream.Position;
214:                   baseStream.Position += ZipConstants.CENHOW ZipConstants.CENVEM;
215:                   
216:                   if (baseStream.Position oldPos != ZipConstants.CENHOW ZipConstants.CENVEM) {
217:                       throw new EndOfStreamException();
218:                   }
219:                   int method ReadLeShort();
220:                   int dostime ReadLeInt();
221:                   int crc ReadLeInt();
222:                   int csize ReadLeInt();
223:                   int size ReadLeInt();
224:                   int nameLen ReadLeShort();
225:                   int extraLen ReadLeShort();
226:                   int commentLen ReadLeShort();
227:                   
228:                   oldPos baseStream.Position;
229:                   baseStream.Position += ZipConstants.CENOFF ZipConstants.CENDSK;
230:                   if (baseStream.Position oldPos != ZipConstants.CENOFF ZipConstants.CENDSK) {
231:                       throw new EndOfStreamException();
232:                   }
233:                   int offset ReadLeInt();
234:                   
235:                   byte[] buffer new byte[Math.Max(nameLencommentLen)];
236:                   
237:                   baseStream.Read(buffer0nameLen);
238:                   string name ZipConstants.ConvertToString(buffer);
239:                   
240:                   ZipEntry entry new ZipEntry(name);
241:                   entry.CompressionMethod = (CompressionMethod)method;
242:                   entry.Crc crc 0xffffffffL;
243:                   entry.Size size 0xffffffffL;
244:                   entry.CompressedSize csize 0xffffffffL;
245:                   entry.DosTime dostime;
246:                   if (extraLen 0) {
247:                       byte[] extra new byte[extraLen];
248:                       baseStream.Read(extra0extraLen);
249:                       entry.ExtraData extra;
250:                   }
251:                   if (commentLen 0) {
252:                       baseStream.Read(buffer0commentLen);
253:                       entry.Comment ZipConstants.ConvertToString(buffer);
254:                   }
255:                   entry.zipFileIndex i;
256:                   entry.offset offset;
257:                   entries[i] = entry;
258:               }
259:           }
260:           
261:           /// <summary>
262:           /// Closes the ZipFile.  This also closes all input streams given by
263:           /// this class.  After this is called, no further method should be
264:           /// called.
265:           /// </summary>
266:           /// <exception name="System.IO.IOException">
267:           /// if a i/o error occured.
268:           /// </exception>
269:           public void Close()
270:           {
271:               entries null;
272:               lock(baseStream) {
273:                   baseStream.Close();
274:               }
275:           }
276:           
277:           /// <summary>
278:           /// Returns an IEnumerator of all Zip entries in this Zip file.
279:           /// </summary>
280:           public IEnumerator GetEnumerator()
281:           {
282:               if (entries == null) {
283:                   throw new InvalidOperationException("ZipFile has closed");
284:               }
285:               
286:               return new ZipEntryEnumeration(entries);
287:           }
288:           
289:           int GetEntryIndex(string name)
290:           {
291:               for (int 0entries.Lengthi++) {
292:                   if (name.Equals(entries[i].Name)) {
293:                       return i;
294:                   }
295:               }
296:               return -1;
297:           }
298:           
299:           /// <summary>
300:           /// Searches for a zip entry in this archive with the given name.
301:           /// </summary>
302:           /// <param name="name">
303:           /// the name. May contain directory components separated by slashes ('/').
304:           /// </param>
305:           /// <returns>
306:           /// the zip entry, or null if no entry with that name exists.
307:           /// </returns>
308:           public ZipEntry GetEntry(string name)
309:           {
310:               if (entries == null) {
311:                   throw new InvalidOperationException("ZipFile has closed");
312:               }
313:               int index GetEntryIndex(name);
314:               return index >= ? (ZipEntryentries[index].Clone() null;
315:           }
316:           
317:           /// <summary>
318:           /// Checks, if the local header of the entry at index i matches the
319:           /// central directory, and returns the offset to the data.
320:           /// </summary>
321:           /// <returns>
322:           /// the start offset of the (compressed) data.
323:           /// </returns>
324:           /// <exception name="System.IO.IOException">
325:           /// if a i/o error occured.
326:           /// </exception>
327:           /// <exception name="ICSharpCode.SharpZipLib.ZipException">
328:           /// if the local header doesn't match the central directory header
329:           /// </exception>
330:           long CheckLocalHeader(ZipEntry entry)
331:           {
332:               lock(baseStream) {
333:                   baseStream.Seek(entry.offsetSeekOrigin.Begin);
334:                   if (ReadLeInt() != ZipConstants.LOCSIG) {
335:                       throw new ZipException("Wrong Local header signature");
336:                   }
337:                   
338:                   /* skip version and flags */
339:                   long oldPos baseStream.Position;
340:                   baseStream.Position += ZipConstants.LOCHOW ZipConstants.LOCVER;
341:                   if (baseStream.Position oldPos != ZipConstants.LOCHOW ZipConstants.LOCVER) {
342:                       throw new EndOfStreamException();
343:                   }
344:                   
345:                   if (entry.CompressionMethod != (CompressionMethod)ReadLeShort()) {
346:                       throw new ZipException("Compression method mismatch");
347:                   }
348:                   
349:                   /* Skip time, crc, size and csize */
350:                   oldPos baseStream.Position;
351:                   baseStream.Position += ZipConstants.LOCNAM ZipConstants.LOCTIM;
352:                   
353:                   if (baseStream.Position oldPos != ZipConstants.LOCNAM ZipConstants.LOCTIM) {
354:                       throw new EndOfStreamException();
355:                   }
356:                   
357:                   if (entry.Name.Length != ReadLeShort()) {
358:                       throw new ZipException("file name length mismatch");
359:                   }
360:                   
361:                   int extraLen entry.Name.Length ReadLeShort();
362:                   return entry.offset ZipConstants.LOCHDR extraLen;
363:               }
364:           }
365:           
366:           /// <summary>
367:           /// Creates an input stream reading the given zip entry as
368:           /// uncompressed data.  Normally zip entry should be an entry
369:           /// returned by GetEntry().
370:           /// </summary>
371:           /// <returns>
372:           /// the input stream.
373:           /// </returns>
374:           /// <exception name="System.IO.IOException">
375:           /// if a i/o error occured.
376:           /// </exception>
377:           /// <exception name="ICSharpCode.SharpZipLib.ZipException">
378:           /// if the Zip archive is malformed.
379:           /// </exception>
380:           public Stream GetInputStream(ZipEntry entry)
381: