PageRenderTime 126ms CodeModel.GetById 60ms app.highlight 22ms RepoModel.GetById 38ms app.codeStats 1ms

/Utilities/Compression/Streams/ZipInputStream.cs

#
C# | 691 lines | 423 code | 76 blank | 192 comment | 95 complexity | eba91b4308abefe2848cb4962e88c40a MD5 | raw file
  1// Based on Mike Krueger's SharpZipLib, Copyright (C) 2001 (GNU license).
  2// Authors of the original java version: Jochen Hoenicke, John Leuner
  3// See http://www.ISeeSharpCode.com for more information.
  4
  5using System;
  6using System.IO;
  7using System.Text;
  8using Delta.Utilities.Compression.Checksums;
  9using Delta.Utilities.Compression.Inflaters;
 10using Delta.Utilities.Helpers;
 11
 12namespace Delta.Utilities.Compression.Streams
 13{
 14
 15	#region Summary
 16	/// <summary>
 17	/// This is an InflaterInputStream that reads the files baseInputStream an
 18	/// zip archive one after another. It has a special method to get the zip
 19	/// entry of the next file. The zip entry contains information about the
 20	/// file name size, compressed size, Crc, etc.
 21	/// It includes support for Stored and Deflated entries.
 22	/// </summary>
 23	/// <example>This sample shows how to read a zip file
 24	/// <code lang="C#">
 25	/// using System;
 26	/// using System.Text;
 27	/// using System.IO;
 28	/// using Delta.Utilities.Compression;
 29	/// 
 30	/// class MainClass
 31	/// {
 32	/// 	public static void Main(string[] args)
 33	/// 	{
 34	/// 		ZipInputStream s = new ZipInputStream(File.OpenRead(args[0]));
 35	/// 		
 36	/// 		ZipEntry theEntry;
 37	/// 		while ((theEntry = s.GetNextEntry()) != null)
 38	///     {
 39	/// 			int size = 2048;
 40	/// 			byte[] data = new byte[2048];
 41	/// 			
 42	/// 			Console.Write("Show contents (y/n) ?");
 43	/// 			if (Console.ReadLine() == "y")
 44	///       {
 45	/// 				while (true)
 46	///         {
 47	/// 					size = s.Read(data, 0, data.Length);
 48	/// 					if (size > 0)
 49	///           {
 50	/// 						Console.Write(new ASCIIEncoding().GetString(data, 0, size));
 51	/// 					} // if
 52	///           else
 53	///           {
 54	/// 						break;
 55	/// 					} // else
 56	/// 				} // while
 57	/// 			} // if
 58	/// 		} // while
 59	/// 		s.Close();
 60	/// 	} // Main(args)
 61	/// }	// class MainClass
 62	/// </code>
 63	/// </example>
 64	#endregion
 65
 66	public class ZipInputStream : InflaterInputStream
 67	{
 68		#region Delegates
 69		/// <summary>
 70		/// Delegate for reading bytes from a stream.
 71		/// </summary>
 72		/// <param name="buffer">Buffer</param>
 73		/// <param name="offset">Offset</param>
 74		/// <param name="length">Length</param>
 75		/// <returns>Number of bytes read</returns>
 76		private delegate int ReaderDelegate(byte[] buffer, int offset, int length);
 77		#endregion
 78
 79		#region Password (Public)
 80		/// <summary>
 81		/// Optional password used for encryption when non-null
 82		/// </summary>
 83		public string Password
 84		{
 85			get
 86			{
 87				return password;
 88			} // get
 89			set
 90			{
 91				password = value;
 92			} // set
 93		}
 94		#endregion
 95
 96		#region CanDecompressEntry (Public)
 97		/// <summary>
 98		/// Gets a value indicating if the entry can be decompressed
 99		/// </summary>
100		/// <remarks>
101		/// The entry can only be decompressed if the library supports the zip
102		/// features required to extract it. See the
103		/// <see cref="ZipEntry.Version">ZipEntry Version</see> property for more
104		/// details.
105		/// </remarks>
106		public bool CanDecompressEntry
107		{
108			get
109			{
110				return entry != null && entry.Version <= ZipConstants.VersionMadeBy;
111			} // get
112		}
113		#endregion
114
115		#region IsEntryAvailable (Public)
116		/// <summary>
117		/// Returns 1 if there is an entry available
118		/// Otherwise returns 0.
119		/// </summary>
120		public override int IsEntryAvailable
121		{
122			get
123			{
124				return entry != null
125				       	? 1
126				       	: 0;
127			} // get
128		}
129		#endregion
130
131		#region Private
132
133		#region internalReader (Private)
134		/// <summary>
135		/// The current reader this instance.
136		/// </summary>
137		private ReaderDelegate internalReader;
138		#endregion
139
140		#region crc (Private)
141		/// <summary>
142		/// Crc32 checksum
143		/// </summary>
144		private Crc32 crc = new Crc32();
145		#endregion
146
147		#region entry (Private)
148		/// <summary>
149		/// Entry
150		/// </summary>
151		private ZipEntry entry;
152		#endregion
153
154		#region size (Private)
155		/// <summary>
156		/// Size
157		/// </summary>
158		private long size;
159		#endregion
160
161		#region method (Private)
162		/// <summary>
163		/// Method
164		/// </summary>
165		private int method;
166		#endregion
167
168		#region flags (Private)
169		/// <summary>
170		/// Flags
171		/// </summary>
172		private int flags;
173		#endregion
174
175		#region password (Private)
176		/// <summary>
177		/// Password
178		/// </summary>
179		private string password;
180		#endregion
181
182		#endregion
183
184		#region Constructors
185		/// <summary>
186		/// Creates a new Zip input stream, for reading a zip archive.
187		/// </summary>
188		public ZipInputStream(Stream baseInputStream)
189			: base(baseInputStream, new Inflater(true))
190		{
191			internalReader = InitialRead;
192		}
193		#endregion
194
195		#region GetNextEntry (Public)
196		/// <summary>
197		/// Advances to the next entry in the archive
198		/// </summary>
199		/// <returns>
200		/// The next <see cref="ZipEntry">entry</see> in the archive or null if
201		/// there are no more entries.
202		/// </returns>
203		/// <remarks>
204		/// If the previous entry is still open
205		/// <see cref="CloseEntry">CloseEntry</see> is called.
206		/// </remarks>
207		/// <exception cref="InvalidOperationException">
208		/// Input stream is closed
209		/// </exception>
210		/// <exception cref="ZipException">
211		/// Password is not set, password is invalid, compression method is
212		/// invalid, version required to extract is not supported.
213		/// </exception>
214		public ZipEntry GetNextEntry()
215		{
216			if (crc == null)
217			{
218				throw new InvalidOperationException("Closed.");
219			}
220
221			if (entry != null)
222			{
223				CloseEntry();
224			}
225
226			int header = inputBuffer.ReadLeInt();
227
228			if (header == ZipConstants.CentralDirectorySig ||
229			    header == ZipConstants.EndSig ||
230			    header == ZipConstants.CentralDigitalSig ||
231			    header == ZipConstants.CentralDirectorySig64)
232			{
233				// No more individual entries exist
234				Close();
235				return null;
236			}
237
238			// -jr- 07-Dec-2003 Ignore spanning temporary signatures if found
239			// SpanningSig is same as descriptor signature and is untested as yet.
240			if (header == ZipConstants.SpanningTempSig ||
241			    header == ZipConstants.SpanningSig)
242			{
243				header = inputBuffer.ReadLeInt();
244			}
245
246			if (header != ZipConstants.LocalSignature)
247			{
248				throw new ZipException(
249					"Wrong Local header signature: 0x" + String.Format("{0:X}", header));
250			}
251
252			short versionRequiredToExtract = (short)inputBuffer.ReadLeShort();
253
254			flags = inputBuffer.ReadLeShort();
255			method = inputBuffer.ReadLeShort();
256			uint dostime = (uint)inputBuffer.ReadLeInt();
257			int crc2 = inputBuffer.ReadLeInt();
258			csize = inputBuffer.ReadLeInt();
259			size = inputBuffer.ReadLeInt();
260			int nameLen = inputBuffer.ReadLeShort();
261			int extraLen = inputBuffer.ReadLeShort();
262
263			bool isCrypted = (flags & 1) == 1;
264
265			byte[] buffer = new byte[nameLen];
266			inputBuffer.ReadRawBuffer(buffer);
267
268			string name = ZipConstants.ConvertToString(buffer);
269
270			entry = new ZipEntry(name, versionRequiredToExtract);
271			entry.Flags = flags;
272
273			if (method == (int)CompressionMethod.Stored &&
274			    (!isCrypted && csize != size ||
275			     (isCrypted && csize - ZipConstants.CryptoHeaderSize != size)))
276			{
277				throw new ZipException("Stored, but compressed != uncompressed");
278			}
279
280			if (method != (int)CompressionMethod.Stored &&
281			    method != (int)CompressionMethod.Deflated)
282			{
283				throw new ZipException("Unknown compression method " + method);
284			}
285
286			entry.CompressionMethod = (CompressionMethod)method;
287
288			if ((flags & 8) == 0)
289			{
290				entry.Crc = crc2 & 0xFFFFFFFFL;
291				entry.Size = size & 0xFFFFFFFFL;
292				entry.CompressedSize = csize & 0xFFFFFFFFL;
293			}
294			else
295			{
296				// This allows for GNU, WinZip and possibly other archives,
297				// the PKZIP spec says these are zero under these circumstances.
298				if (crc2 != 0)
299				{
300					entry.Crc = crc2 & 0xFFFFFFFFL;
301				}
302
303				if (size != 0)
304				{
305					entry.Size = size & 0xFFFFFFFFL;
306				}
307				if (csize != 0)
308				{
309					entry.CompressedSize = csize & 0xFFFFFFFFL;
310				}
311			}
312
313			entry.DosTime = dostime;
314
315			if (extraLen > 0)
316			{
317				byte[] extra = new byte[extraLen];
318				inputBuffer.ReadRawBuffer(extra);
319				entry.SetExtraData(extra);
320			}
321
322			internalReader = InitialRead;
323			return entry;
324		}
325		#endregion
326
327		#region CloseEntry (Public)
328		/// <summary>
329		/// Closes the current zip entry and moves to the next one.
330		/// </summary>
331		/// <exception cref="InvalidOperationException">
332		/// The stream is closed
333		/// </exception>
334		/// <exception cref="ZipException">
335		/// The Zip stream ends early
336		/// </exception>
337		public void CloseEntry()
338		{
339			if (crc == null)
340			{
341				throw new InvalidOperationException("Closed.");
342			}
343
344			if (entry == null)
345			{
346				return;
347			}
348
349			if (method == (int)CompressionMethod.Deflated)
350			{
351				if ((flags & 8) != 0)
352				{
353					// We don't know how much we must skip, read until end.
354					byte[] tmp = new byte[2048];
355					while (Read(tmp, 0, tmp.Length) > 0)
356					{
357						;
358					}
359					// read will close this entry
360					return;
361				}
362				csize -= inf.TotalIn;
363				inputBuffer.Available -= inf.RemainingInput;
364			}
365
366			if (inputBuffer.Available > csize && csize >= 0)
367			{
368				inputBuffer.Available = (int)(inputBuffer.Available - csize);
369			}
370			else
371			{
372				csize -= inputBuffer.Available;
373				inputBuffer.Available = 0;
374				while (csize != 0)
375				{
376					int skipped = (int)base.Skip(csize & 0xFFFFFFFFL);
377
378					if (skipped <= 0)
379					{
380						throw new ZipException("Zip archive ends early.");
381					}
382
383					csize -= skipped;
384				}
385			}
386
387			size = 0;
388			crc.Reset();
389			if (method == (int)CompressionMethod.Deflated)
390			{
391				inf.Reset();
392			}
393			entry = null;
394		}
395		#endregion
396
397		#region ReadByte (Public)
398		/// <summary>
399		/// Reads a byte from the current zip entry.
400		/// </summary>
401		/// <returns>
402		/// The byte or -1 if end of stream is reached.
403		/// </returns>
404		/// <exception name="System.IO.IOException">
405		/// An i/o error occured.
406		/// </exception>
407		/// <exception name="Delta.Utilities.Compression.ZipException">
408		/// The deflated stream is corrupted.
409		/// </exception>
410		public override int ReadByte()
411		{
412			byte[] b = new byte[1];
413			if (Read(b, 0, 1) <= 0)
414			{
415				return MathHelper.InvalidIndex;
416			}
417			return b[0] & 0xff;
418		}
419		#endregion
420
421		#region Read (Public)
422		/// <summary>
423		/// Read a block of bytes from the stream.
424		/// </summary>
425		/// <param name="buffer">The destination for the bytes.</param>
426		/// <param name="offset">The index to start storing data.</param>
427		/// <param name="count">The number of bytes to attempt to read.</param>
428		/// <returns>Returns the number of bytes read.</returns>
429		/// <remarks>Zero bytes read means end of stream.</remarks>
430		public override int Read(byte[] buffer, int offset, int count)
431		{
432			return internalReader(buffer, offset, count);
433		}
434		#endregion
435
436		#region BodyRead (Public)
437		/// <summary>
438		/// Reads a block of bytes from the current zip entry.
439		/// </summary>
440		/// <returns>
441		/// The number of bytes read (this may be less than the length requested,
442		/// even before the end of stream), or 0 on end of stream.
443		/// </returns>
444		/// <exception name="IOException">
445		/// An i/o error occured.
446		/// </exception>
447		/// <exception cref="ZipException">
448		/// The deflated stream is corrupted.
449		/// </exception>
450		/// <exception cref="InvalidOperationException">
451		/// The stream is not open.
452		/// </exception>
453		public int BodyRead(byte[] b, int off, int len)
454		{
455			if (crc == null)
456			{
457				throw new InvalidOperationException("Closed.");
458			}
459
460			if (entry == null ||
461			    len <= 0)
462			{
463				return 0;
464			}
465
466			bool finished = false;
467
468			switch (method)
469			{
470				case (int)CompressionMethod.Deflated:
471					len = base.Read(b, off, len);
472					if (len <= 0)
473					{
474						if (inf.IsFinished == false)
475						{
476							throw new ZipException("Inflater not finished!?");
477						}
478						inputBuffer.Available = inf.RemainingInput;
479
480						if ((flags & 8) == 0 &&
481						    (inf.TotalIn != csize ||
482						     inf.TotalOut != size))
483						{
484							throw new ZipException("size mismatch: " + csize + ";" + size +
485							                       " <-> " + inf.TotalIn + ";" + inf.TotalOut);
486						}
487						inf.Reset();
488						finished = true;
489					}
490					break;
491
492				case (int)CompressionMethod.Stored:
493					if (len > csize && csize >= 0)
494					{
495						len = (int)csize;
496					}
497					len = inputBuffer.ReadClearTextBuffer(b, off, len);
498					if (len > 0)
499					{
500						csize -= len;
501						size -= len;
502					}
503
504					if (csize == 0)
505					{
506						finished = true;
507					}
508					else
509					{
510						if (len < 0)
511						{
512							throw new ZipException("EOF in stored block");
513						}
514					}
515					break;
516			}
517
518			if (len > 0)
519			{
520				crc.Update(b, off, len);
521			}
522
523			if (finished)
524			{
525				StopDecrypting();
526
527				if ((flags & 8) != 0)
528				{
529					ReadDataDescriptor();
530				}
531
532				if ((crc.Value & 0xFFFFFFFFL) != entry.Crc &&
533				    entry.Crc != MathHelper.InvalidIndex)
534				{
535					throw new ZipException("CRC mismatch");
536				}
537				crc.Reset();
538				entry = null;
539			}
540			return len;
541		}
542		#endregion
543
544		#region Close (Public)
545		/// <summary>
546		/// Closes the zip input stream
547		/// </summary>
548		public override void Close()
549		{
550			base.Close();
551			crc = null;
552			entry = null;
553		}
554		#endregion
555
556		#region ExtractZipEntry (Public)
557		/// <summary>
558		/// Extract zip currentEntry
559		/// </summary>
560		/// <param name="zipEntry">Zip entry</param>
561		/// <returns>Memory stream</returns>
562		public MemoryStream ExtractZipEntry(ZipEntry zipEntry)
563		{
564			if (zipEntry == null)
565			{
566				throw new ArgumentNullException("entry",
567					"ZipEntry must be valid");
568			}
569
570			// Max. length for reading a block of data: 4096
571			// If we use a greater value we get errors like a lot of 0'str ..
572			const int MaxReadBlockLength = 4096; //2048;
573			int length = MaxReadBlockLength;
574			byte[] data = new Byte[length];
575
576			MemoryStream memStream = new MemoryStream();
577			do
578			{
579				length = Read(data, 0, data.Length);
580				if (length > 0)
581				{
582					memStream.Write(data, 0, length);
583				}
584			} while (length > 0);
585
586			// Always go to beginning of stream for reading!
587			memStream.Position = 0;
588			return memStream;
589		}
590		#endregion
591
592		#region Methods (Private)
593
594		#region ReadDataDescriptor
595		/// <summary>
596		/// Read data descriptor at the end of compressed data.
597		/// </summary>
598		private void ReadDataDescriptor()
599		{
600			if (inputBuffer.ReadLeInt() != ZipConstants.ExternSig)
601			{
602				throw new ZipException("Data descriptor signature not found");
603			}
604
605			entry.Crc = inputBuffer.ReadLeInt() & 0xFFFFFFFFL;
606			csize = inputBuffer.ReadLeInt();
607			size = inputBuffer.ReadLeInt();
608
609			entry.Size = size & 0xFFFFFFFFL;
610			entry.CompressedSize = csize & 0xFFFFFFFFL;
611		}
612		#endregion
613
614		#region InitialRead
615		/// <summary>
616		/// Perform the initial read on an entry which may include
617		/// reading encryption headers and setting up inflation.
618		/// </summary>
619		/// <param name="destination">Destination</param>
620		/// <param name="offset">Offset</param>
621		/// <param name="count">Count</param>
622		/// <returns>Int</returns>
623		private int InitialRead(byte[] destination, int offset, int count)
624		{
625			if (entry.Version > ZipConstants.VersionMadeBy)
626			{
627				throw new ZipException(
628					"Library cannot extract this entry version required (" +
629					entry.Version.ToString() + ")");
630			}
631
632			// test for encryption
633			if (entry.IsCrypted)
634			{
635				if (password == null)
636				{
637					throw new ZipException("No password set.");
638				}
639
640				// Generate and set crypto transform...
641				ZipEncryptionManaged managed = new ZipEncryptionManaged();
642				byte[] key = ZipEncryption.GenerateKeys(
643					Encoding.ASCII.GetBytes(password));
644
645				inputBuffer.CryptoTransform = managed.CreateDecryptor(key, null);
646
647				byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize];
648				inputBuffer.ReadClearTextBuffer(cryptbuffer, 0,
649					ZipConstants.CryptoHeaderSize);
650
651				if ((flags & 8) == 0)
652				{
653					if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] !=
654					    (byte)(entry.Crc >> 24))
655					{
656						throw new ZipException("Invalid password");
657					} // if
658				} // if
659				else
660				{
661					if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] !=
662					    (byte)((entry.DosTime >> 8) & 0xff))
663					{
664						throw new ZipException("Invalid password");
665					} // if
666				} // else
667
668				if (csize >= ZipConstants.CryptoHeaderSize)
669				{
670					csize -= ZipConstants.CryptoHeaderSize;
671				} // if
672			}
673			else
674			{
675				inputBuffer.CryptoTransform = null;
676			} // else
677
678			if (method == (int)CompressionMethod.Deflated &&
679			    inputBuffer.Available > 0)
680			{
681				inputBuffer.SetInflaterInput(inf);
682			} // if
683
684			internalReader = BodyRead;
685			return BodyRead(destination, offset, count);
686		}
687		#endregion
688
689		#endregion
690	}
691}