PageRenderTime 154ms CodeModel.GetById 60ms app.highlight 57ms RepoModel.GetById 24ms app.codeStats 0ms

/Utilities/Compression/ZipFile.cs

#
C# | 1404 lines | 842 code | 133 blank | 429 comment | 101 complexity | f98daee5f9f389405594378514151dd0 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.Collections;
   7using System.IO;
   8using System.Runtime.CompilerServices;
   9using System.Security.Cryptography;
  10using System.Text;
  11using Delta.Utilities.Compression.Checksums;
  12using Delta.Utilities.Compression.Inflaters;
  13using Delta.Utilities.Compression.Streams;
  14using Delta.Utilities.Helpers;
  15
  16namespace Delta.Utilities.Compression
  17{
  18	/// <summary>
  19	/// This class represents a Zip archive.  You can ask for the contained
  20	/// entries, or get an input stream for a file entry.  The entry is
  21	/// automatically decompressed.
  22	/// 
  23	/// This class is thread safe: You can open input streams for arbitrary
  24	/// entries in different threads.
  25	/// </summary>
  26	/// <example>
  27	/// <code>
  28	/// using System;
  29	/// using System.Text;
  30	/// using System.Collections;
  31	/// using System.IO;
  32	/// 
  33	/// using Delta.Utilities.Compression;
  34	/// 
  35	/// class MainClass
  36	/// {
  37	/// 	static public void Main(string[] args)
  38	/// 	{
  39	/// 		ZipFile zFile = new ZipFile(args[0]);
  40	/// 		Console.WriteLine("Listing of : " + zFile.Name);
  41	/// 		Console.WriteLine("");
  42	/// 		Console.WriteLine("Raw Size    Size      Date     Time     Name");
  43	/// 		Console.WriteLine("--------  --------  --------  ------  ---------");
  44	/// 		foreach (ZipEntry e in zFile)
  45	///     {
  46	/// 			DateTime d = e.DateTime;
  47	/// 			Console.WriteLine("{0, -10}{1, -10}{2}  {3}   {4}",
  48	///         e.Size, e.CompressedSize,
  49	/// 			  d.ToString("dd-MM-yy"), d.ToString("t"),
  50	/// 			  e.Name);
  51	/// 		} // foreach
  52	/// 	} // Main(args)
  53	/// } // class MainClass
  54	/// </code>
  55	/// </example>
  56	public class ZipFile : IEnumerable, IDisposable
  57	{
  58		#region ZipEntryEnumeration Class
  59		/// <summary>
  60		/// Zip entry enumeration
  61		/// </summary>
  62		private class ZipEntryEnumeration : IEnumerator
  63		{
  64			#region Current (Public)
  65			/// <summary>
  66			/// Current
  67			/// </summary>
  68			/// <returns>Object</returns>
  69			public object Current
  70			{
  71				get
  72				{
  73					return array[ptr];
  74				}
  75			}
  76			#endregion
  77
  78			#region Private
  79
  80			#region array (Private)
  81			/// <summary>
  82			/// Array
  83			/// </summary>
  84			private readonly ZipEntry[] array;
  85			#endregion
  86
  87			#region ptr (Private)
  88			/// <summary>
  89			/// Pointer to entry as an integer
  90			/// </summary>
  91			/// <returns>-</returns>
  92			private int ptr = -1;
  93			#endregion
  94
  95			#endregion
  96
  97			#region Constructors
  98			/// <summary>
  99			/// Create zip entry enumeration
 100			/// </summary>
 101			/// <param name="arr">Array</param>
 102			public ZipEntryEnumeration(ZipEntry[] arr)
 103			{
 104				array = arr;
 105			}
 106			#endregion
 107
 108			#region IEnumerator Members
 109			/// <summary>
 110			/// Move next
 111			/// </summary>
 112			/// <returns>True if there is more to enumerate</returns>
 113			public bool MoveNext()
 114			{
 115				return (++ptr < array.Length);
 116			}
 117
 118			/// <summary>
 119			/// Reset
 120			/// </summary>
 121			public void Reset()
 122			{
 123				ptr = -1;
 124			}
 125			#endregion
 126		}
 127		#endregion
 128
 129		#region PartialInputStream Class
 130		/// <summary>
 131		/// Partial input stream
 132		/// </summary>
 133		/// <returns>Inflater input stream</returns>
 134		private class PartialInputStream : InflaterInputStream
 135		{
 136			#region IsEntryAvailable (Public)
 137			/// <summary>
 138			/// Available
 139			/// </summary>
 140			public override int IsEntryAvailable
 141			{
 142				get
 143				{
 144					long amount = end - filepos;
 145					if (amount > Int32.MaxValue)
 146					{
 147						return Int32.MaxValue;
 148					}
 149
 150					return (int)amount;
 151				}
 152			}
 153			#endregion
 154
 155			#region Private
 156
 157			#region baseStream (Private)
 158			/// <summary>
 159			/// Base stream
 160			/// </summary>
 161			private readonly Stream baseStream;
 162			#endregion
 163
 164			#region end (Private)
 165			/// <summary>
 166			/// File position
 167			/// </summary>
 168			private readonly long end;
 169			#endregion
 170
 171			#region filepos (Private)
 172			/// <summary>
 173			/// File position
 174			/// </summary>
 175			private long filepos;
 176			#endregion
 177
 178			#endregion
 179
 180			#region Constructors
 181			/// <summary>
 182			/// Create partial input stream
 183			/// </summary>
 184			/// <param name="baseStream">Base stream</param>
 185			/// <param name="start">Start</param>
 186			/// <param name="len">Len</param>
 187			public PartialInputStream(Stream baseStream, long start, long len)
 188				: base(baseStream)
 189			{
 190				this.baseStream = baseStream;
 191				filepos = start;
 192				end = start + len;
 193			}
 194			#endregion
 195
 196			#region ReadByte (Public)
 197			/// <summary>
 198			/// Read a byte from this stream.
 199			/// </summary>
 200			/// <returns>Returns the byte read or -1 on end of stream.</returns>
 201			public override int ReadByte()
 202			{
 203				if (filepos == end)
 204				{
 205					return -1; //ok
 206				}
 207
 208				//why? This produces CA2002: Do not lock on objects with weak identity
 209				//lock (baseStream)
 210				//{
 211				baseStream.Seek(filepos++, SeekOrigin.Begin);
 212				return baseStream.ReadByte();
 213				//} // lock
 214			}
 215			#endregion
 216
 217			#region Close (Public)
 218			/// <summary>
 219			/// Close this partial input stream.
 220			/// </summary>
 221			/// <remarks>
 222			/// The underlying stream is not closed.
 223			/// Close the parent ZipFile class to do that.
 224			/// </remarks>
 225			public override void Close()
 226			{
 227				// Do nothing at all!
 228			}
 229			#endregion
 230
 231			#region Read (Public)
 232			/// <summary>
 233			/// Read
 234			/// </summary>
 235			/// <param name="b">B</param>
 236			/// <param name="off">Off</param>
 237			/// <param name="len">Len</param>
 238			/// <returns>Int</returns>
 239			public override int Read(byte[] b, int off, int len)
 240			{
 241				if (len > end - filepos)
 242				{
 243					len = (int)(end - filepos);
 244					if (len == 0)
 245					{
 246						return 0;
 247					}
 248				}
 249
 250				//why? This produces CA2002: Do not lock on objects with weak identity
 251				//lock (baseStream)
 252				//{
 253				baseStream.Seek(filepos, SeekOrigin.Begin);
 254				int count = baseStream.Read(b, off, len);
 255				if (count > 0)
 256				{
 257					filepos += len;
 258				}
 259				return count;
 260				//} // lock
 261			}
 262			#endregion
 263
 264			#region SkipBytes (Public)
 265			/// <summary>
 266			/// Skip bytes
 267			/// </summary>
 268			/// <param name="amount">Amount</param>
 269			/// <returns>Long</returns>
 270			public long SkipBytes(long amount)
 271			{
 272				if (amount < 0)
 273				{
 274					throw new ArgumentOutOfRangeException();
 275				}
 276				if (amount > end - filepos)
 277				{
 278					amount = end - filepos;
 279				}
 280				filepos += amount;
 281				return amount;
 282			}
 283			#endregion
 284		}
 285		#endregion
 286
 287		#region Delegates
 288		/// <summary>
 289		/// Delegate for handling keys/password setting during
 290		/// compression / decompression.
 291		/// </summary>
 292		public delegate void KeysRequiredEventHandler(
 293			object sender,
 294			KeysRequiredEventArgs e
 295			);
 296		#endregion
 297
 298		#region Open (Static)
 299		/// <summary>
 300		/// Opens a zip file with the given name for reading an returns it or
 301		/// 'null' if the file is invalid.
 302		/// </summary>
 303		/// <param name="zipFilePath">Zip file path</param>
 304		public static ZipFile Open(string zipFilePath)
 305		{
 306			if (FileHelper.Exists(zipFilePath) == false)
 307			{
 308				Log.Warning("Can't open the zip file '" + zipFilePath +
 309				            "' because it doesn't exists");
 310				return null;
 311			} // if
 312
 313			ZipFile zipFile = null;
 314			try
 315			{
 316				FileStream zipFileStream = FileHelper.Open(zipFilePath);
 317				zipFile = new ZipFile(zipFileStream)
 318				{
 319					FilePath = zipFilePath,
 320				};
 321
 322				zipFile.ReadEntries();
 323			} // try
 324			catch (Exception ex)
 325			{
 326				Log.Warning("Couldn't open the zip file '" + zipFilePath +
 327				            "' because of reason: '" + ex.Message + "'");
 328				zipFile.Close();
 329				zipFile = null;
 330			} // catch
 331
 332			return zipFile;
 333		}
 334		#endregion
 335
 336		#region Create (Static)
 337		/// <summary>
 338		/// Creates a zip file with the given name and returns it or 'null' if the
 339		/// file can't be created for some reason.
 340		/// </summary>
 341		/// <param name="newZipFilePath">New zip file path</param>
 342		/// <param name="overrideIfExists">Override if exists</param>
 343		/// <returns>Zip file</returns>
 344		public static ZipFile Create(string newZipFilePath,
 345			bool overrideIfExists = false)
 346		{
 347			FileMode fileCreationMode = FileMode.CreateNew;
 348
 349			if (FileHelper.Exists(newZipFilePath))
 350			{
 351				if (overrideIfExists)
 352				{
 353					// Switch to "override file" mode
 354					fileCreationMode = FileMode.Create;
 355				} // if
 356				else
 357				{
 358					Log.Warning("Can't create zip file '" + newZipFilePath +
 359					            "' because it already exists");
 360					return null;
 361				} // else
 362			} // if
 363
 364			ZipFile zipFile = null;
 365			try
 366			{
 367				FileStream zipFileStream = FileHelper.Open(newZipFilePath,
 368					fileCreationMode, FileAccess.ReadWrite, FileShare.None);
 369				zipFile = new ZipFile(zipFileStream)
 370				{
 371					FilePath = newZipFilePath,
 372				};
 373				zipFile.zipOutput = new ZipOutputStream(zipFile.baseStream);
 374				// Compression: 0=none - 9=best
 375				zipFile.zipOutput.SetCompressionLevel(9);
 376			} // try
 377			catch (Exception ex)
 378			{
 379				Log.Warning("Couldn't create the zip file '" + newZipFilePath +
 380				            "' because of reason: '" + ex.Message + "'");
 381			} // catch
 382
 383			return zipFile;
 384		}
 385		#endregion
 386
 387		#region ZipFileComment (Public)
 388		/// <summary>
 389		/// Gets the comment for the zip file.
 390		/// </summary>
 391		public string ZipFileComment
 392		{
 393			get
 394			{
 395				return comment;
 396			} // get
 397		}
 398		#endregion
 399
 400		#region FilePath (Public)
 401		/// <summary>
 402		/// The path of this zip file.
 403		/// </summary>
 404		public string FilePath
 405		{
 406			get;
 407			private set;
 408		}
 409		#endregion
 410
 411		#region Size (Public)
 412		/// <summary>
 413		/// Gets the size (in bytes) of this zip file.
 414		/// </summary>
 415		/// <exception cref="InvalidOperationException">
 416		/// The Zip file has been closed.
 417		/// </exception>
 418		public long Size
 419		{
 420			get
 421			{
 422				if (baseStream != null)
 423				{
 424					return baseStream.Length;
 425				} // if
 426				else
 427				{
 428					throw new InvalidOperationException("ZipFile is closed");
 429				} // else
 430			} // get
 431		}
 432		#endregion
 433
 434		#region Password (Public)
 435		/// <summary>
 436		/// Password to be used for encrypting/decrypting files.
 437		/// </summary>
 438		/// <remarks>Set to null if no password is required.</remarks>
 439		public string Password
 440		{
 441			set
 442			{
 443				if (value == null ||
 444				    value.Length == 0)
 445				{
 446					key = null;
 447				} // if
 448				else
 449				{
 450					key = ZipEncryption.GenerateKeys(Encoding.ASCII.GetBytes(value));
 451				} // else
 452			} // set
 453		}
 454		#endregion
 455
 456		#region EntryByIndex (Public)
 457		/// <summary>
 458		/// Indexer property for ZipEntries
 459		/// </summary>
 460		[IndexerName("EntryByIndex")]
 461		public ZipEntry this[int index]
 462		{
 463			get
 464			{
 465				return entries[index].Clone();
 466			} // get
 467		}
 468		#endregion
 469
 470		#region zipOutput (Public)
 471		/// <summary>
 472		/// Zip output
 473		/// </summary>
 474		public ZipOutputStream zipOutput;
 475		#endregion
 476
 477		#region KeysRequired (Public)
 478		/// <summary>
 479		/// Event handler for handling encryption keys.
 480		/// </summary>
 481		public KeysRequiredEventHandler KeysRequired;
 482		#endregion
 483
 484		#region Private
 485
 486		#region baseStream (Private)
 487		/// <summary>
 488		/// Base Stream
 489		/// </summary>
 490		private readonly Stream baseStream;
 491		#endregion
 492
 493		#region comment (Private)
 494		/// <summary>
 495		/// File Comment
 496		/// </summary>
 497		private string comment;
 498		#endregion
 499
 500		#region isStreamOwner (Private)
 501		/// <summary>
 502		/// Get/set a flag indicating if the underlying stream is owned by the
 503		/// ZipFile instance. If the flag is true then the stream will be closed
 504		/// when <see cref="Close">Close</see> is called.
 505		/// </summary>
 506		/// <remarks>
 507		/// The default value is true in all cases.
 508		/// </remarks>
 509		private bool isStreamOwner = true;
 510		#endregion
 511
 512		#region offsetOfFirstEntry (Private)
 513		/// <summary>
 514		/// Offset of first entry
 515		/// </summary>
 516		/// <returns>0</returns>
 517		private long offsetOfFirstEntry;
 518		#endregion
 519
 520		#region entries (Private)
 521		/// <summary>
 522		/// Entries
 523		/// </summary>
 524		private ZipEntry[] entries;
 525		#endregion
 526
 527		#region key (Private)
 528		/// <summary>
 529		/// The encryption key value.
 530		/// </summary>
 531		/// <returns>Null</returns>
 532		private byte[] key;
 533		#endregion
 534
 535		#region HaveKeys (Private)
 536		/// <summary>
 537		/// Have keys
 538		/// </summary>
 539		/// <returns>Does this zip file entry have keys?</returns>
 540		private bool HaveKeys
 541		{
 542			get
 543			{
 544				return key != null;
 545			} // get
 546		}
 547		#endregion
 548
 549		#endregion
 550
 551		#region Constructors
 552		/// <summary>
 553		/// Opens a Zip file reading the given Stream
 554		/// </summary>
 555		/// <exception cref="ZipException">
 556		/// The file doesn't contain a valid zip archive.
 557		/// <para />
 558		/// The stream provided cannot seek.
 559		/// </exception>
 560		private ZipFile(Stream setStream)
 561		{
 562			baseStream = setStream;
 563		}
 564		#endregion
 565
 566		#region IDisposable Members
 567		/// <summary>
 568		/// Dispose
 569		/// </summary>
 570		public void Dispose()
 571		{
 572			Close();
 573		}
 574		#endregion
 575
 576		#region IEnumerable Members
 577		/// <summary>
 578		/// Returns an enumerator for the Zip entries in this Zip file.
 579		/// </summary>
 580		/// <exception cref="InvalidOperationException">
 581		/// The Zip file has been closed.
 582		/// </exception>
 583		public IEnumerator GetEnumerator()
 584		{
 585			if (entries == null)
 586			{
 587				throw new InvalidOperationException("ZipFile has closed");
 588			}
 589
 590			return new ZipEntryEnumeration(entries);
 591		}
 592		#endregion
 593
 594		#region Close (Public)
 595		/// <summary>
 596		/// Closes the ZipFile. If the stream is
 597		/// <see cref="isStreamOwner">owned</see> then this also closes the
 598		/// underlying input stream. Once closed, no further instance methods
 599		/// should be called.
 600		/// </summary>
 601		/// <exception cref="System.IO.IOException">
 602		/// An i/o error occurs.
 603		/// </exception>
 604		public void Close()
 605		{
 606			entries = null;
 607			if (isStreamOwner)
 608			{
 609				/*produces warning CA2002: Do not lock on objects with weak identity
 610				lock (baseStream)
 611				{
 612					baseStream.Close();
 613				} // lock
 614				*/
 615
 616				if (zipOutput != null)
 617				{
 618					zipOutput.Finish();
 619					zipOutput.Close();
 620					zipOutput = null;
 621				} // if (zipOutput)
 622
 623				baseStream.Close();
 624			}
 625		}
 626		#endregion
 627
 628		#region FindEntry (Public)
 629		/// <summary>
 630		/// Return the index of the entry with a matching name
 631		/// </summary>
 632		/// <param name="searchName">Entry name to find</param>
 633		/// <param name="ignoreCase">If true the comparison is case insensitive
 634		/// </param>
 635		/// <returns>The index position of the matching entry or -1 if not found
 636		/// </returns>
 637		/// <exception cref="InvalidOperationException">
 638		/// The Zip file has been closed.
 639		/// </exception>
 640		public int FindEntry(string searchName, bool ignoreCase)
 641		{
 642			if (entries == null)
 643			{
 644				throw new InvalidOperationException("ZipFile has been closed");
 645			} // if
 646
 647			for (int index = 0; index < entries.Length; index++)
 648			{
 649				if (string.Compare(searchName, entries[index].Name, ignoreCase) == 0)
 650				{
 651					return index;
 652				} // if
 653			} // for
 654
 655			return MathHelper.InvalidIndex;
 656		}
 657		#endregion
 658
 659		#region GetEntry (Public)
 660		/// <summary>
 661		/// Searches for a zip entry in this archive with the given name.
 662		/// String comparisons are case insensitive
 663		/// </summary>
 664		/// <param name="searchName">
 665		/// The name to find. May contain directory components separated by
 666		/// slashes ('/').
 667		/// </param>
 668		/// <returns>
 669		/// The zip entry, or null if no entry with that name exists.
 670		/// </returns>
 671		/// <exception cref="InvalidOperationException">
 672		/// The Zip file has been closed.
 673		/// </exception>
 674		public ZipEntry GetEntry(string searchName)
 675		{
 676			if (entries == null)
 677			{
 678				throw new InvalidOperationException("ZipFile has been closed");
 679			} // if
 680
 681			int index = FindEntry(searchName, true);
 682			return (index >= 0)
 683			       	? entries[index].Clone()
 684			       	: null;
 685		}
 686		#endregion
 687
 688		#region TestArchive (Public)
 689		/// <summary>
 690		/// Test an archive for integrity/validity
 691		/// </summary>
 692		/// <param name="testData">Perform low level data Crc check</param>
 693		/// <returns>true if the test passes, false otherwise</returns>
 694		public bool TestArchive(bool testData)
 695		{
 696			bool result = true;
 697			try
 698			{
 699				for (int index = 0; index < entries.Length; ++index)
 700				{
 701					long localOffset = CheckLocalHeader(this[index], true, true);
 702					if (testData)
 703					{
 704						Stream entryStream = GetInputStream(index);
 705						// Note: Test events for updating info, recording errors etc
 706						Crc32 crc = new Crc32();
 707						byte[] buffer = new byte[4096];
 708						int bytesRead;
 709						while ((bytesRead =
 710							entryStream.Read(buffer, 0, buffer.Length)) > 0)
 711						{
 712							crc.Update(buffer, 0, bytesRead);
 713						} // while
 714
 715						if (this[index].Crc != crc.Value)
 716						{
 717							result = false;
 718							Log.Warning(
 719								"Crc failed, expected " + this[index].Crc +
 720								", but calculated " + crc.Value);
 721							break;
 722						} // if
 723					} // if
 724				} // for
 725			} // try
 726			catch
 727			{
 728				result = false;
 729			} // catch
 730
 731			return result;
 732		}
 733		#endregion
 734
 735		#region GetInputStream (Public)
 736		/// <summary>
 737		/// Creates an input stream reading a zip entry
 738		/// </summary>
 739		/// <param name="entryIndex">The index of the entry to obtain an input
 740		/// stream for.</param>
 741		/// <returns>
 742		/// An input stream.
 743		/// </returns>
 744		/// <exception cref="InvalidOperationException">
 745		/// The ZipFile has already been closed
 746		/// </exception>
 747		/// <exception cref="Delta.Utilities.Compression.ZipException">
 748		/// The compression method for the entry is unknown
 749		/// </exception>
 750		/// <exception cref="IndexOutOfRangeException">
 751		/// The entry is not found in the ZipFile
 752		/// </exception>
 753		public Stream GetInputStream(int entryIndex)
 754		{
 755			if (entries == null)
 756			{
 757				throw new InvalidOperationException("ZipFile has closed");
 758			} // if
 759
 760			long start = CheckLocalHeader(entries[entryIndex]);
 761			CompressionMethod method = entries[entryIndex].CompressionMethod;
 762			Stream istr = new PartialInputStream(
 763				baseStream, start, entries[entryIndex].CompressedSize);
 764
 765			if (entries[entryIndex].IsCrypted)
 766			{
 767				istr = CreateAndInitDecryptionStream(istr, entries[entryIndex]);
 768				if (istr == null)
 769				{
 770					throw new ZipException("Unable to decrypt this entry");
 771				} // if
 772			} // if
 773
 774			switch (method)
 775			{
 776				case CompressionMethod.Stored:
 777					return istr;
 778				case CompressionMethod.Deflated:
 779					return new InflaterInputStream(istr, new Inflater(true));
 780				default:
 781					throw new ZipException(
 782						"Unsupported compression method " + method);
 783			} // switch
 784		}
 785		#endregion
 786
 787		#region Append (Public)
 788		public void Append(string filePath, string entryName = null)
 789		{
 790			if (FileHelper.Exists(filePath) == false)
 791			{
 792				throw new FileNotFoundException("Unable to add file '" + filePath +
 793				                                "' to zip '" + this +
 794				                                "' because the file was not found!");
 795			}
 796			if (zipOutput == null)
 797			{
 798				throw new InvalidOperationException("ZipFile has closed or was not " +
 799				                                    "created for adding zip entries!");
 800			}
 801
 802			if (entryName == null)
 803			{
 804				entryName = FileHelper.TryToUseRelativePath(filePath);
 805			}
 806
 807			try
 808			{
 809				Stream fs = new FileStream(filePath, FileMode.Open,
 810					FileAccess.Read, FileShare.Read);
 811
 812				byte[] buffer = new byte[fs.Length];
 813				fs.Read(buffer, 0, buffer.Length);
 814				fs.Close();
 815
 816				ZipEntry newEntry = new ZipEntry(entryName);
 817				zipOutput.PutNextEntry(newEntry);
 818				zipOutput.Write(buffer, 0, buffer.Length);
 819			} // try
 820			catch (Exception ex)
 821			{
 822				Log.Warning("Could not save file '" + filePath +
 823				            "' into zip (entryName=" + entryName + "): " + ex);
 824			} // catch
 825		}
 826		#endregion
 827
 828		#region Methods (Private)
 829
 830		#region OnKeysRequired
 831		/// <summary>
 832		/// Handles getting of encryption keys when required.
 833		/// </summary>
 834		/// <param name="fileName">The file for which encryptino keys are required.
 835		/// </param>
 836		private void OnKeysRequired(string fileName)
 837		{
 838			if (KeysRequired != null)
 839			{
 840				KeysRequiredEventArgs krea = new KeysRequiredEventArgs(fileName, key);
 841				KeysRequired(this, krea);
 842				key = krea.GetKey();
 843			}
 844		}
 845		#endregion
 846
 847		#region ReadEntries
 848		/// <summary>
 849		/// Search for and read the central directory of a zip file filling the
 850		/// entries array. This is called exactly once by the constructors.
 851		/// </summary>
 852		/// <exception cref="System.IO.IOException">
 853		/// An i/o error occurs.
 854		/// </exception>
 855		/// <exception cref="Delta.Utilities.Compression.ZipException">
 856		/// The central directory is malformed or cannot be found
 857		/// </exception>
 858		private void ReadEntries()
 859		{
 860			// Search for the End Of Central Directory.  When a zip comment is
 861			// present the directory may start earlier.
 862			// 
 863			// Note: The search is limited to 64K which is the maximum size of a
 864			// trailing comment field to aid speed. This should be compatible with
 865			// both SFX and ZIP files but has only been tested for Zip files.
 866			// Need to confirm this is valid in all cases.
 867			// Could also speed this up by reading memory in larger blocks.			
 868
 869			if (baseStream.CanSeek == false)
 870			{
 871				throw new ZipException("ZipFile stream must be seekable");
 872			} // if
 873
 874			long locatedCentralDirOffset = LocateBlockWithSignature(
 875				ZipConstants.EndSig, baseStream.Length,
 876				ZipConstants.EndHeader, 0xffff);
 877			if (locatedCentralDirOffset < 0)
 878			{
 879				throw new ZipException("Cannot find central directory");
 880			} // if
 881
 882			//unused: int thisDiskNumber =
 883			ReadLeShort();
 884			//unused: int startCentralDirDisk =
 885			ReadLeShort();
 886			int entriesForThisDisk = ReadLeShort();
 887			int entriesForWholeCentralDir = ReadLeShort();
 888			int centralDirSize = ReadLeInt();
 889			int offsetOfCentralDir = ReadLeInt();
 890			int commentSize = ReadLeShort();
 891
 892			byte[] zipComment = new byte[commentSize];
 893			baseStream.Read(zipComment, 0, zipComment.Length);
 894			comment = ZipConstants.ConvertToString(zipComment);
 895
 896			/* Its seems possible that this is too strict, more digging required.
 897			if (thisDiskNumber != 0 ||
 898				startCentralDirDisk != 0 ||
 899				entriesForThisDisk != entriesForWholeCentralDir)
 900			{
 901				throw new ZipException("Spanned archives are not currently handled");
 902			}
 903			*/
 904
 905			entries = new ZipEntry[entriesForWholeCentralDir];
 906
 907			// SFX support, find the offset of the first entry vis the start of the
 908			// stream. This applies to Zip files that are appended to the end of the
 909			// SFX stub. Zip files created by some archivers have the offsets altered
 910			// to reflect the true offsets and so dont require any adjustment here...
 911			if (offsetOfCentralDir < locatedCentralDirOffset - (4 + centralDirSize))
 912			{
 913				offsetOfFirstEntry = locatedCentralDirOffset -
 914				                     (4 + centralDirSize + offsetOfCentralDir);
 915				if (offsetOfFirstEntry <= 0)
 916				{
 917					throw new ZipException("Invalid SFX file");
 918				} // if
 919			} // if
 920
 921			baseStream.Seek(offsetOfFirstEntry + offsetOfCentralDir,
 922				SeekOrigin.Begin);
 923
 924			for (int entryId = 0; entryId < entriesForThisDisk; entryId++)
 925			{
 926				if (ReadLeInt() !=
 927				    ZipConstants.CentralDirectorySig)
 928				{
 929					throw new ZipException("Wrong Central Directory signature");
 930				} // if
 931
 932				int hostSystemId = ReadLeShort();
 933				int entryVersion = ReadLeShort();
 934				int bitFlags = ReadLeShort();
 935				int method = ReadLeShort();
 936				int dostime = ReadLeInt();
 937				int crc = ReadLeInt();
 938				int csize = ReadLeInt();
 939				int size = ReadLeInt();
 940				int nameLength = ReadLeShort();
 941				int extraLength = ReadLeShort();
 942				int commentLength = ReadLeShort();
 943
 944				// Not currently used
 945				//unused: int diskStartNo =
 946				ReadLeShort();
 947				// Not currently used
 948				//unused: int internalAttributes =
 949				ReadLeShort();
 950
 951				int externalAttributes = ReadLeInt();
 952				int offset = ReadLeInt();
 953
 954				byte[] buffer = new byte[Math.Max(nameLength, commentLength)];
 955
 956				baseStream.Read(buffer, 0, nameLength);
 957				string entryName = ZipConstants.ConvertToString(buffer, nameLength);
 958
 959				ZipEntry entry = new ZipEntry(entryName, entryVersion, hostSystemId)
 960				{
 961					CompressionMethod = (CompressionMethod)method,
 962					Crc = crc & 0xffffffffL,
 963					Size = size & 0xffffffffL,
 964					CompressedSize = csize & 0xffffffffL,
 965					Flags = bitFlags,
 966					DosTime = (uint)dostime
 967				};
 968
 969				if (extraLength > 0)
 970				{
 971					byte[] extra = new byte[extraLength];
 972					baseStream.Read(extra, 0, extraLength);
 973					entry.SetExtraData(extra);
 974				} // if
 975
 976				if (commentLength > 0)
 977				{
 978					baseStream.Read(buffer, 0, commentLength);
 979					entry.Comment = ZipConstants.ConvertToString(buffer, commentLength);
 980				} // if
 981
 982				entry.ZipFileIndex = entryId;
 983				entry.Offset = offset;
 984				entry.ExternalFileAttributes = externalAttributes;
 985
 986				entries[entryId] = entry;
 987			} // for
 988		}
 989		#endregion
 990
 991		#region ReadLeShort
 992		/// <summary>
 993		/// Read an unsigned short in little endian byte order.
 994		/// </summary>
 995		/// <returns>Returns the value read.</returns>
 996		/// <exception cref="IOException">
 997		/// An i/o error occurs.
 998		/// </exception>
 999		/// <exception cref="EndOfStreamException">
1000		/// The file ends prematurely
1001		/// </exception>
1002		private int ReadLeShort()
1003		{
1004			return baseStream.ReadByte() | baseStream.ReadByte() << 8;
1005		}
1006		#endregion
1007
1008		#region ReadLeInt
1009		/// <summary>
1010		/// Read an int in little endian byte order.
1011		/// </summary>
1012		/// <returns>Returns the value read.</returns>
1013		/// <exception cref="IOException">
1014		/// An i/o error occurs.
1015		/// </exception>
1016		/// <exception cref="System.IO.EndOfStreamException">
1017		/// The file ends prematurely
1018		/// </exception>
1019		private int ReadLeInt()
1020		{
1021			return ReadLeShort() | ReadLeShort() << 16;
1022		}
1023		#endregion
1024
1025		#region LocateBlockWithSignature
1026		/// <summary>
1027		/// NOTE this returns the offset of the first byte after the signature.
1028		/// </summary>
1029		/// <param name="signature">Signature</param>
1030		/// <param name="endLocation">End location</param>
1031		/// <param name="minimumBlockSize">Minimum block size</param>
1032		/// <param name="maximumVariableData">Maximum variable data</param>
1033		/// <returns>Long</returns>
1034		private long LocateBlockWithSignature(int signature, long endLocation,
1035			int minimumBlockSize, int maximumVariableData)
1036		{
1037			long pos = endLocation - minimumBlockSize;
1038			if (pos < 0)
1039			{
1040				return MathHelper.InvalidIndex;
1041			} // if
1042
1043			long giveUpMarker = Math.Max(pos - maximumVariableData, 0);
1044
1045			// Note: this loop could be optimized for speed.
1046			do
1047			{
1048				if (pos < giveUpMarker)
1049				{
1050					return MathHelper.InvalidIndex;
1051				}
1052				baseStream.Seek(pos--, SeekOrigin.Begin);
1053			} while (ReadLeInt() != signature);
1054
1055			return baseStream.Position;
1056		}
1057		#endregion
1058
1059		#region CheckLocalHeader
1060		/// <summary>
1061		/// Checks, if the local header of the entry at index i matches the
1062		/// central directory, and returns the offset to the data.
1063		/// </summary>
1064		/// <param name="entry">
1065		/// The entry to test against
1066		/// </param>
1067		/// <param name="fullTest">
1068		/// False by default. If set to true, the check will be extremely picky
1069		/// about the testing, otherwise be relaxed.
1070		/// </param>
1071		/// <param name="extractTest">
1072		/// Apply extra testing to see if the entry can be extracted by the
1073		/// library. True by default.
1074		/// </param>
1075		/// <returns>
1076		/// The start offset of the (compressed) data.
1077		/// </returns>
1078		/// <exception cref="System.IO.EndOfStreamException">
1079		/// The stream ends prematurely
1080		/// </exception>
1081		/// <exception cref="Delta.Utilities.Compression.ZipException">
1082		/// The local header signature is invalid, the entry and central header
1083		/// file name lengths are different or the local and entry compression
1084		/// methods dont match.
1085		/// </exception>
1086		private long CheckLocalHeader(ZipEntry entry, bool fullTest = false,
1087			bool extractTest = true)
1088		{
1089			baseStream.Seek(offsetOfFirstEntry + entry.Offset, SeekOrigin.Begin);
1090			if (ReadLeInt() !=
1091					ZipConstants.LocalSignature)
1092			{
1093				throw new ZipException(
1094					"Wrong local header signature");
1095			} // if
1096
1097			// version required to extract
1098			short shortValue = (short)ReadLeShort();
1099			if (extractTest && shortValue > ZipConstants.VersionMadeBy)
1100			{
1101				throw new ZipException(
1102					"Version required to extract this entry not supported (" +
1103					shortValue + ")");
1104			} // if
1105
1106			// general purpose bit flags.
1107			short localFlags = (short)ReadLeShort();
1108			if (extractTest)
1109			{
1110				if ((localFlags &
1111						 (int)
1112						 (GeneralBitFlags.Patched |
1113							GeneralBitFlags.StrongEncryption |
1114							GeneralBitFlags.EnhancedCompress |
1115							GeneralBitFlags.HeaderMasked)) != 0)
1116				{
1117					throw new ZipException(
1118						"The library doesnt support the zip version required to " +
1119						"extract this entry");
1120				} // if
1121			} // if
1122
1123			if (localFlags != entry.Flags)
1124			{
1125				throw new ZipException(
1126					"Central header/local header flags mismatch");
1127			} // if
1128
1129			if (entry.CompressionMethod !=
1130					(CompressionMethod)ReadLeShort())
1131			{
1132				throw new ZipException(
1133					"Central header/local header compression method mismatch");
1134			} // if
1135
1136			// file time
1137			shortValue = (short)ReadLeShort();
1138			// file date
1139			shortValue = (short)ReadLeShort();
1140
1141			// Crc
1142			int intValue = ReadLeInt();
1143
1144			if (fullTest)
1145			{
1146				if ((localFlags &
1147						 (int)GeneralBitFlags.Descriptor) == 0)
1148				{
1149					if (intValue != (int)entry.Crc)
1150					{
1151						throw new ZipException(
1152							"Central header/local header crc mismatch");
1153					}
1154				} // if
1155			} // if
1156
1157			// compressed Size
1158			intValue = ReadLeInt();
1159			// uncompressed size
1160			intValue = ReadLeInt();
1161
1162			// Note: make test more correct...  can't compare lengths as was done
1163			// originally as this can fail for MBCS strings. Assuming a code page
1164			// at this point is not valid?  Best is to store the name length in
1165			// the ZipEntry probably.
1166			int storedNameLength = ReadLeShort();
1167			if (entry.Name.Length > storedNameLength)
1168			{
1169				throw new ZipException("file name length mismatch");
1170			} // if
1171
1172			int extraLen = storedNameLength + ReadLeShort();
1173			return
1174				offsetOfFirstEntry + entry.Offset + ZipConstants.LocalHeader +
1175				extraLen;
1176		}
1177		#endregion
1178
1179		#region ReadFully
1180		/// <summary>
1181		/// Refactor this, its done elsewhere as well
1182		/// </summary>
1183		/// <param name="stream">S</param>
1184		/// <param name="outBuf">Out buf</param>
1185		private void ReadFully(Stream stream, byte[] outBuf)
1186		{
1187			int offset = 0;
1188			int length = outBuf.Length;
1189			while (length > 0)
1190			{
1191				int count = stream.Read(outBuf, offset, length);
1192				if (count <= 0)
1193				{
1194					throw new ZipException("Unexpected EOF");
1195				}
1196				offset += count;
1197				length -= count;
1198			}
1199		}
1200		#endregion
1201
1202		#region CheckClassicPassword
1203		/// <summary>
1204		/// Check classic password
1205		/// </summary>
1206		/// <param name="classicCryptoStream">Classic crypto stream</param>
1207		/// <param name="entry">Entry</param>
1208		private void CheckClassicPassword(CryptoStream classicCryptoStream,
1209			ZipEntry entry)
1210		{
1211			byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize];
1212			ReadFully(classicCryptoStream, cryptbuffer);
1213
1214			if ((entry.Flags & (int)GeneralBitFlags.Descriptor) == 0)
1215			{
1216				if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] !=
1217				    (byte)(entry.Crc >> 24))
1218				{
1219					throw new ZipException("Invalid password");
1220				} // if
1221			} // if
1222			else
1223			{
1224				if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] !=
1225				    (byte)((entry.DosTime >> 8) & 0xff))
1226				{
1227					throw new ZipException("Invalid password");
1228				} // if
1229			} // else
1230		}
1231		#endregion
1232
1233		#region CreateAndInitDecryptionStream
1234		/// <summary>
1235		/// Create and init decryption stream
1236		/// </summary>
1237		/// <param name="setBaseStream">Base stream</param>
1238		/// <param name="entry">Entry</param>
1239		/// <returns>Stream</returns>
1240		private Stream CreateAndInitDecryptionStream(Stream setBaseStream,
1241			ZipEntry entry)
1242		{
1243			CryptoStream result = null;
1244
1245			if (entry.Version < ZipConstants.VersionStrongEncryption ||
1246			    (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0)
1247			{
1248				ZipEncryptionManaged classicManaged = new ZipEncryptionManaged();
1249
1250				OnKeysRequired(entry.Name);
1251				if (HaveKeys == false)
1252				{
1253					throw new ZipException(
1254						"No password available for encrypted stream");
1255				} // if
1256
1257				result = new CryptoStream(setBaseStream,
1258					classicManaged.CreateDecryptor(key, null), CryptoStreamMode.Read);
1259				CheckClassicPassword(result, entry);
1260			} // if
1261			else
1262			{
1263				throw new ZipException(
1264					"Decryption method not supported");
1265			} // else
1266
1267			return result;
1268		}
1269		#endregion
1270
1271		#region WriteEncryptionHeader
1272		/// <summary>
1273		/// Write encryption header
1274		/// </summary>
1275		/// <param name="stream">Stream</param>
1276		/// <param name="crcValue">Crc value</param>
1277		private void WriteEncryptionHeader(Stream stream, long crcValue)
1278		{
1279			byte[] cryptBuffer = new byte[ZipConstants.CryptoHeaderSize];
1280			Random rnd = new Random();
1281			rnd.NextBytes(cryptBuffer);
1282			cryptBuffer[11] = (byte)(crcValue >> 24);
1283			stream.Write(cryptBuffer, 0, cryptBuffer.Length);
1284		}
1285		#endregion
1286
1287		#region CreateAndInitEncryptionStream
1288		/// <summary>
1289		/// Create and init encryption stream
1290		/// </summary>
1291		/// <param name="setBaseStream">Base stream</param>
1292		/// <param name="entry">Entry</param>
1293		/// <returns>Stream</returns>
1294		private Stream CreateAndInitEncryptionStream(Stream setBaseStream,
1295			ZipEntry entry)
1296		{
1297			CryptoStream result = null;
1298			if (entry.Version < ZipConstants.VersionStrongEncryption ||
1299			    (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0)
1300			{
1301				ZipEncryptionManaged classicManaged = new ZipEncryptionManaged();
1302
1303				OnKeysRequired(entry.Name);
1304				if (HaveKeys == false)
1305				{
1306					throw new ZipException(
1307						"No password available for encrypted stream");
1308				} // if
1309
1310				result = new CryptoStream(setBaseStream,
1311					classicManaged.CreateEncryptor(key, null), CryptoStreamMode.Write);
1312				if (entry.Crc < 0 ||
1313				    (entry.Flags & 8) != 0)
1314				{
1315					WriteEncryptionHeader(result, entry.DosTime << 16);
1316				} // if
1317				else
1318				{
1319					WriteEncryptionHeader(result, entry.Crc);
1320				} // else
1321			} // if
1322			return result;
1323		}
1324		#endregion
1325
1326		#region GetOutputStream
1327		/// <summary>
1328		/// Gets an output stream for the specified <see cref="ZipEntry"/>
1329		/// </summary>
1330		/// <param name="entry">The entry to get an output stream for.</param>
1331		/// <param name="fileName"></param>
1332		/// <returns>The output stream obtained for the entry.</returns>
1333		private Stream GetOutputStream(ZipEntry entry, string fileName)
1334		{
1335			baseStream.Seek(0, SeekOrigin.End);
1336			Stream result = File.OpenWrite(fileName);
1337
1338			if (entry.IsCrypted)
1339			{
1340				result = CreateAndInitEncryptionStream(result, entry);
1341			}
1342
1343			switch (entry.CompressionMethod)
1344			{
1345				case CompressionMethod.Stored:
1346					break;
1347
1348				case CompressionMethod.Deflated:
1349					result = new DeflaterOutputStream(result);
1350					break;
1351
1352				default:
1353					throw new ZipException(
1354						"Unknown compression method " + entry.CompressionMethod);
1355			} // switch
1356			return result;
1357		}
1358		#endregion
1359
1360		#region GetInputStream
1361		/// <summary>
1362		/// Creates an input stream reading the given zip entry as
1363		/// uncompressed data. Normally zip entry should be an entry
1364		/// returned by GetEntry().
1365		/// </summary>
1366		/// <returns>
1367		/// the input stream.
1368		/// </returns>
1369		/// <exception cref="InvalidOperationException">
1370		/// The ZipFile has already been closed
1371		/// </exception>
1372		/// <exception cref="Delta.Utilities.Compression.ZipException">
1373		/// The compression method for the entry is unknown
1374		/// </exception>
1375		/// <exception cref="IndexOutOfRangeException">
1376		/// The entry is not found in the ZipFile
1377		/// </exception>
1378		private Stream GetInputStream(ZipEntry entry)
1379		{
1380			if (entries == null)
1381			{
1382				throw new InvalidOperationException("ZipFile has closed");
1383			} // if
1384
1385			int index = entry.ZipFileIndex;
1386			if (index < 0 ||
1387			    index >= entries.Length ||
1388			    entries[index].Name != entry.Name)
1389			{
1390				index = FindEntry(entry.Name, true);
1391				if (index < 0)
1392				{
1393					throw new IndexOutOfRangeException();
1394				} // if
1395			} // if
1396
1397			return GetInputStream(index);
1398		}
1399		#endregion
1400
1401		#endregion
1402	}
1403}
1404