1 // ZipInputStream.cs
2 //
3 // Copyright (C) 2001 Mike Krueger
4 // Copyright (C) 2004 John Reilly
5 //
6 // This file was translated from java, it was part of the GNU Classpath
7 // Copyright (C) 2001 Free Software Foundation, Inc.
8 //
9 // This program is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU General Public License
11 // as published by the Free Software Foundation; either version 2
12 // of the License, or (at your option) any later version.
13 //
14 // This program is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 // GNU General Public License for more details.
18 //
19 // You should have received a copy of the GNU General Public License
20 // along with this program; if not, write to the Free Software
21 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22 //
23 // Linking this library statically or dynamically with other modules is
24 // making a combined work based on this library.  Thus, the terms and
25 // conditions of the GNU General Public License cover the whole
26 // combination.
27 //
28 // As a special exception, the copyright holders of this library give you
29 // permission to link this library with independent modules to produce an
30 // executable, regardless of the license terms of these independent
31 // modules, and to copy and distribute the resulting executable under
32 // terms of your choice, provided that you also meet, for each linked
33 // independent module, the terms and conditions of the license of that
34 // module.  An independent module is a module which is not derived from
35 // or based on this library.  If you modify this library, you may extend
36 // this exception to your version of the library, but you are not
37 // obligated to do so.  If you do not wish to do so, delete this
38 // exception statement from your version.
39 
40 using System;
41 using System.Text;
42 using System.IO;
43 
44 using ICSharpCode.SharpZipLib.Checksums;
45 using ICSharpCode.SharpZipLib.Zip.Compression;
46 using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
47 using ICSharpCode.SharpZipLib.Encryption;
48 
49 namespace ICSharpCode.SharpZipLib.Zip
50 {
51 	/// <summary>
52 	/// This is an InflaterInputStream that reads the files baseInputStream an zip archive
53 	/// one after another.  It has a special method to get the zip entry of
54 	/// the next file.  The zip entry contains information about the file name
55 	/// size, compressed size, Crc, etc.
56 	/// It includes support for Stored and Deflated entries.
57 	/// <br/>
58 	/// <br/>Author of the original java version : Jochen Hoenicke
59 	/// </summary>
60 	///
61 	/// <example> This sample shows how to read a zip file
62 	/// <code lang="C#">
63 	/// using System;
64 	/// using System.Text;
65 	/// using System.IO;
66 	///
67 	/// using ICSharpCode.SharpZipLib.Zip;
68 	///
69 	/// class MainClass
70 	/// {
71 	/// 	public static void Main(string[] args)
72 	/// 	{
73 	/// 		ZipInputStream s = new ZipInputStream(File.OpenRead(args[0]));
74 	///
75 	/// 		ZipEntry theEntry;
76 	/// 		while ((theEntry = s.GetNextEntry()) != null) {
77 	/// 			int size = 2048;
78 	/// 			byte[] data = new byte[2048];
79 	///
80 	/// 			Console.Write("Show contents (y/n) ?");
81 	/// 			if (Console.ReadLine() == "y") {
82 	/// 				while (true) {
83 	/// 					size = s.Read(data, 0, data.Length);
84 	/// 					if (size > 0) {
85 	/// 						Console.Write(new ASCIIEncoding().GetString(data, 0, size));
86 	/// 					} else {
87 	/// 						break;
88 	/// 					}
89 	/// 				}
90 	/// 			}
91 	/// 		}
92 	/// 		s.Close();
93 	/// 	}
94 	/// }
95 	/// </code>
96 	/// </example>
97 	[System.ObsoleteAttribute("This assembly has been deprecated. Please use https://www.nuget.org/packages/SharpZipLib/ instead.")]
98 	public class ZipInputStream : InflaterInputStream
99 	{
100 		// Delegate for reading bytes from a stream.
ReaderDelegate(byte[] b, int offset, int length)101 		delegate int ReaderDelegate(byte[] b, int offset, int length);
102 
103 		/// <summary>
104 		/// The current reader this instance.
105 		/// </summary>
106 		ReaderDelegate internalReader;
107 
108 		Crc32 crc = new Crc32();
109 		ZipEntry entry = null;
110 
111 		long size;
112 		int method;
113 		int flags;
114 		string password = null;
115 
116 		/// <summary>
117 		/// Creates a new Zip input stream, for reading a zip archive.
118 		/// </summary>
ZipInputStream(Stream baseInputStream)119 		public ZipInputStream(Stream baseInputStream) : base(baseInputStream, new Inflater(true))
120 		{
121 			internalReader = new ReaderDelegate(InitialRead);
122 		}
123 
124 
125 		/// <summary>
126 		/// Optional password used for encryption when non-null
127 		/// </summary>
128 		public string Password
129 		{
130 			get {
131 				return password;
132 			}
133 			set {
134 				password = value;
135 			}
136 		}
137 
138 
139 		/// <summary>
140 		/// Gets a value indicating if the entry can be decompressed
141 		/// </summary>
142 		/// <remarks>
143 		/// The entry can only be decompressed if the library supports the zip features required to extract it.
144 		/// See the <see cref="ZipEntry.Version">ZipEntry Version</see> property for more details.
145 		/// </remarks>
146 		public bool CanDecompressEntry {
147 			get {
148 				return entry != null && entry.Version <= ZipConstants.VERSION_MADE_BY;
149 			}
150 		}
151 
152 		/// <summary>
153 		/// Advances to the next entry in the archive
154 		/// </summary>
155 		/// <returns>
156 		/// The next <see cref="ZipEntry">entry</see> in the archive or null if there are no more entries.
157 		/// </returns>
158 		/// <remarks>
159 		/// If the previous entry is still open <see cref="CloseEntry">CloseEntry</see> is called.
160 		/// </remarks>
161 		/// <exception cref="InvalidOperationException">
162 		/// Input stream is closed
163 		/// </exception>
164 		/// <exception cref="ZipException">
165 		/// Password is not set, password is invalid, compression method is invalid,
166 		/// version required to extract is not supported
167 		/// </exception>
GetNextEntry()168 		public ZipEntry GetNextEntry()
169 		{
170 			if (crc == null) {
171 				throw new InvalidOperationException("Closed.");
172 			}
173 
174 			if (entry != null) {
175 				CloseEntry();
176 			}
177 
178 			int header = inputBuffer.ReadLeInt();
179 
180 			if (header == ZipConstants.CENSIG ||
181 			    header == ZipConstants.ENDSIG ||
182 			    header == ZipConstants.CENDIGITALSIG ||
183 			    header == ZipConstants.CENSIG64) {
184 			    // No more individual entries exist
185 				Close();
186 				return null;
187 			}
188 
189 			// -jr- 07-Dec-2003 Ignore spanning temporary signatures if found
190 			// SPANNINGSIG is same as descriptor signature and is untested as yet.
191 			if (header == ZipConstants.SPANTEMPSIG || header == ZipConstants.SPANNINGSIG) {
192 				header = inputBuffer.ReadLeInt();
193 			}
194 
195 			if (header != ZipConstants.LOCSIG) {
196 				throw new ZipException("Wrong Local header signature: 0x" + String.Format("{0:X}", header));
197 			}
198 
199 			short versionRequiredToExtract = (short)inputBuffer.ReadLeShort();
200 
201 			flags          = inputBuffer.ReadLeShort();
202 			method         = inputBuffer.ReadLeShort();
203 			uint dostime   = (uint)inputBuffer.ReadLeInt();
204 			int crc2       = inputBuffer.ReadLeInt();
205 			csize          = inputBuffer.ReadLeInt();
206 			size           = inputBuffer.ReadLeInt();
207 			int nameLen    = inputBuffer.ReadLeShort();
208 			int extraLen   = inputBuffer.ReadLeShort();
209 
210 			bool isCrypted = (flags & 1) == 1;
211 
212 			byte[] buffer = new byte[nameLen];
213 			inputBuffer.ReadRawBuffer(buffer);
214 
215 			string name = ZipConstants.ConvertToString(buffer);
216 
217 			entry = new ZipEntry(name, versionRequiredToExtract);
218 			entry.Flags = flags;
219 
220 			if (method == (int)CompressionMethod.Stored && (!isCrypted && csize != size || (isCrypted && csize - ZipConstants.CRYPTO_HEADER_SIZE != size))) {
221 				throw new ZipException("Stored, but compressed != uncompressed");
222 			}
223 
224 			if (method != (int)CompressionMethod.Stored && method != (int)CompressionMethod.Deflated) {
225 				throw new ZipException("Unknown compression method " + method);
226 			}
227 
228 			entry.CompressionMethod = (CompressionMethod)method;
229 
230 			if ((flags & 8) == 0) {
231 				entry.Crc  = crc2 & 0xFFFFFFFFL;
232 				entry.Size = size & 0xFFFFFFFFL;
233 				entry.CompressedSize = csize & 0xFFFFFFFFL;
234 			} else {
235 
236 				// This allows for GNU, WinZip and possibly other archives, the PKZIP spec says these are zero
237 				// under these circumstances.
238 				if (crc2 != 0) {
239 					entry.Crc = crc2 & 0xFFFFFFFFL;
240 				}
241 
242 				if (size != 0) {
243 					entry.Size = size & 0xFFFFFFFFL;
244 				}
245 				if (csize != 0) {
246 					entry.CompressedSize = csize & 0xFFFFFFFFL;
247 				}
248 			}
249 
250 			entry.DosTime = dostime;
251 
252 			if (extraLen > 0) {
253 				byte[] extra = new byte[extraLen];
254 				inputBuffer.ReadRawBuffer(extra);
255 				entry.ExtraData = extra;
256 			}
257 
258 			internalReader = new ReaderDelegate(InitialRead);
259 			return entry;
260 		}
261 
262 		// Read data descriptor at the end of compressed data.
ReadDataDescriptor()263 		void ReadDataDescriptor()
264 		{
265 			if (inputBuffer.ReadLeInt() != ZipConstants.EXTSIG) {
266 				throw new ZipException("Data descriptor signature not found");
267 			}
268 
269 			entry.Crc = inputBuffer.ReadLeInt() & 0xFFFFFFFFL;
270 			csize = inputBuffer.ReadLeInt();
271 			size = inputBuffer.ReadLeInt();
272 
273 			entry.Size = size & 0xFFFFFFFFL;
274 			entry.CompressedSize = csize & 0xFFFFFFFFL;
275 		}
276 
277 		/// <summary>
278 		/// Closes the current zip entry and moves to the next one.
279 		/// </summary>
280 		/// <exception cref="InvalidOperationException">
281 		/// The stream is closed
282 		/// </exception>
283 		/// <exception cref="ZipException">
284 		/// The Zip stream ends early
285 		/// </exception>
CloseEntry()286 		public void CloseEntry()
287 		{
288 			if (crc == null) {
289 				throw new InvalidOperationException("Closed.");
290 			}
291 
292 			if (entry == null) {
293 				return;
294 			}
295 
296 			if (method == (int)CompressionMethod.Deflated) {
297 				if ((flags & 8) != 0) {
298 					// We don't know how much we must skip, read until end.
299 					byte[] tmp = new byte[2048];
300 					while (Read(tmp, 0, tmp.Length) > 0)
301 						;
302 					// read will close this entry
303 					return;
304 				}
305 				csize -= inf.TotalIn;
306 				inputBuffer.Available -= inf.RemainingInput;
307 			}
308 
309 			if (inputBuffer.Available > csize && csize >= 0) {
310 				inputBuffer.Available = (int)((long)inputBuffer.Available - csize);
311 			} else {
312 				csize -= inputBuffer.Available;
313 				inputBuffer.Available = 0;
314 				while (csize != 0) {
315 					int skipped = (int)base.Skip(csize & 0xFFFFFFFFL);
316 
317 					if (skipped <= 0) {
318 						throw new ZipException("Zip archive ends early.");
319 					}
320 
321 					csize -= skipped;
322 				}
323 			}
324 
325 			size = 0;
326 			crc.Reset();
327 			if (method == (int)CompressionMethod.Deflated) {
328 				inf.Reset();
329 			}
330 			entry = null;
331 		}
332 
333 		/// <summary>
334 		/// Returns 1 if there is an entry available
335 		/// Otherwise returns 0.
336 		/// </summary>
337 		public override int Available {
338 			get {
339 				return entry != null ? 1 : 0;
340 			}
341 		}
342 
343 		/// <summary>
344 		/// Reads a byte from the current zip entry.
345 		/// </summary>
346 		/// <returns>
347 		/// The byte or -1 if end of stream is reached.
348 		/// </returns>
349 		/// <exception name="System.IO.IOException">
350 		/// An i/o error occured.
351 		/// </exception>
352 		/// <exception name="ICSharpCode.SharpZipLib.ZipException">
353 		/// The deflated stream is corrupted.
354 		/// </exception>
ReadByte()355 		public override int ReadByte()
356 		{
357 			byte[] b = new byte[1];
358 			if (Read(b, 0, 1) <= 0) {
359 				return -1;
360 			}
361 			return b[0] & 0xff;
362 		}
363 
364 		// Perform the initial read on an entry which may include
365 		// reading encryption headers and setting up inflation.
InitialRead(byte[] destination, int offset, int count)366 		int InitialRead(byte[] destination, int offset, int count)
367 		{
368 			if (entry.Version > ZipConstants.VERSION_MADE_BY) {
369 				throw new ZipException("Libray cannot extract this entry version required (" + entry.Version.ToString() + ")");
370 			}
371 
372 			// test for encryption
373 			if (entry.IsCrypted) {
374 
375 				if (password == null) {
376 					throw new ZipException("No password set.");
377 				}
378 
379 				// Generate and set crypto transform...
380 				PkzipClassicManaged managed = new PkzipClassicManaged();
381 				byte[] key = PkzipClassic.GenerateKeys(Encoding.ASCII.GetBytes(password));
382 
383 				inputBuffer.CryptoTransform = managed.CreateDecryptor(key, null);
384 
385 				byte[] cryptbuffer = new byte[ZipConstants.CRYPTO_HEADER_SIZE];
386 				inputBuffer.ReadClearTextBuffer(cryptbuffer, 0, ZipConstants.CRYPTO_HEADER_SIZE);
387 
388 				if ((flags & 8) == 0) {
389 					if (cryptbuffer[ZipConstants.CRYPTO_HEADER_SIZE - 1] != (byte)(entry.Crc >> 24)) {
390 						throw new ZipException("Invalid password");
391 					}
392 				}
393 				else {
394 					if (cryptbuffer[ZipConstants.CRYPTO_HEADER_SIZE - 1] != (byte)((entry.DosTime >> 8) & 0xff)) {
395 						throw new ZipException("Invalid password");
396 					}
397 				}
398 
399 				if (csize >= ZipConstants.CRYPTO_HEADER_SIZE) {
400 					csize -= ZipConstants.CRYPTO_HEADER_SIZE;
401 				}
402 			}
403 			else {
404 				inputBuffer.CryptoTransform = null;
405 			}
406 
407 			if (method == (int)CompressionMethod.Deflated && inputBuffer.Available > 0) {
408 				inputBuffer.SetInflaterInput(inf);
409 			}
410 
411 			internalReader = new ReaderDelegate(BodyRead);
412 			return BodyRead(destination, offset, count);
413 		}
414 
415 
416 		/// <summary>
417 		/// Read a block of bytes from the stream.
418 		/// </summary>
419 		/// <param name="destination">The destination for the bytes.</param>
420 		/// <param name="index">The index to start storing data.</param>
421 		/// <param name="count">The number of bytes to attempt to read.</param>
422 		/// <returns>Returns the number of bytes read.</returns>
423 		/// <remarks>Zero bytes read means end of stream.</remarks>
Read(byte[] destination, int index, int count)424 		public override int Read(byte[] destination, int index, int count)
425 		{
426 			return internalReader(destination, index, count);
427 		}
428 
429 		/// <summary>
430 		/// Reads a block of bytes from the current zip entry.
431 		/// </summary>
432 		/// <returns>
433 		/// The number of bytes read (this may be less than the length requested, even before the end of stream), or 0 on end of stream.
434 		/// </returns>
435 		/// <exception name="IOException">
436 		/// An i/o error occured.
437 		/// </exception>
438 		/// <exception cref="ZipException">
439 		/// The deflated stream is corrupted.
440 		/// </exception>
441 		/// <exception cref="InvalidOperationException">
442 		/// The stream is not open.
443 		/// </exception>
BodyRead(byte[] b, int off, int len)444 		public int BodyRead(byte[] b, int off, int len)
445 		{
446 			if (crc == null) {
447 				throw new InvalidOperationException("Closed.");
448 			}
449 
450 			if (entry == null || len <= 0 ) {
451 				return 0;
452 			}
453 
454 			bool finished = false;
455 
456 			switch (method) {
457 				case (int)CompressionMethod.Deflated:
458 					len = base.Read(b, off, len);
459 					if (len <= 0) {
460 						if (!inf.IsFinished) {
461 							throw new ZipException("Inflater not finished!?");
462 						}
463 						inputBuffer.Available = inf.RemainingInput;
464 
465 						if ((flags & 8) == 0 && (inf.TotalIn != csize || inf.TotalOut != size)) {
466 							throw new ZipException("size mismatch: " + csize + ";" + size + " <-> " + inf.TotalIn + ";" + inf.TotalOut);
467 						}
468 						inf.Reset();
469 						finished = true;
470 					}
471 					break;
472 
473 				case (int)CompressionMethod.Stored:
474 					if (len > csize && csize >= 0) {
475 						len = (int)csize;
476 					}
477 					len = inputBuffer.ReadClearTextBuffer(b, off, len);
478 					if (len > 0) {
479 						csize -= len;
480 						size -= len;
481 					}
482 
483 					if (csize == 0) {
484 						finished = true;
485 					} else {
486 						if (len < 0) {
487 							throw new ZipException("EOF in stored block");
488 						}
489 					}
490 					break;
491 			}
492 
493 			if (len > 0) {
494 				crc.Update(b, off, len);
495 			}
496 
497 			if (finished) {
498 				StopDecrypting();
499 
500 				if ((flags & 8) != 0) {
501 					ReadDataDescriptor();
502 				}
503 
504 				if ((crc.Value & 0xFFFFFFFFL) != entry.Crc && entry.Crc != -1) {
505 					throw new ZipException("CRC mismatch");
506 				}
507 				crc.Reset();
508 				entry = null;
509 			}
510 			return len;
511 		}
512 
513 		/// <summary>
514 		/// Closes the zip input stream
515 		/// </summary>
Close()516 		public override void Close()
517 		{
518 			base.Close();
519 			crc = null;
520 			entry = null;
521 		}
522 	}
523 }
524