/Utilities/Compression/ZipFile.cs
C# | 1404 lines | 842 code | 133 blank | 429 comment | 101 complexity | f98daee5f9f389405594378514151dd0 MD5 | raw file
Possible License(s): Apache-2.0
- // Based on Mike Krueger's SharpZipLib, Copyright (C) 2001 (GNU license).
- // Authors of the original java version: Jochen Hoenicke, John Leuner
- // See http://www.ISeeSharpCode.com for more information.
-
- using System;
- using System.Collections;
- using System.IO;
- using System.Runtime.CompilerServices;
- using System.Security.Cryptography;
- using System.Text;
- using Delta.Utilities.Compression.Checksums;
- using Delta.Utilities.Compression.Inflaters;
- using Delta.Utilities.Compression.Streams;
- using Delta.Utilities.Helpers;
-
- namespace Delta.Utilities.Compression
- {
- /// <summary>
- /// This class represents a Zip archive. You can ask for the contained
- /// entries, or get an input stream for a file entry. The entry is
- /// automatically decompressed.
- ///
- /// This class is thread safe: You can open input streams for arbitrary
- /// entries in different threads.
- /// </summary>
- /// <example>
- /// <code>
- /// using System;
- /// using System.Text;
- /// using System.Collections;
- /// using System.IO;
- ///
- /// using Delta.Utilities.Compression;
- ///
- /// class MainClass
- /// {
- /// static public void Main(string[] args)
- /// {
- /// ZipFile zFile = new ZipFile(args[0]);
- /// Console.WriteLine("Listing of : " + zFile.Name);
- /// Console.WriteLine("");
- /// Console.WriteLine("Raw Size Size Date Time Name");
- /// Console.WriteLine("-------- -------- -------- ------ ---------");
- /// foreach (ZipEntry e in zFile)
- /// {
- /// DateTime d = e.DateTime;
- /// Console.WriteLine("{0, -10}{1, -10}{2} {3} {4}",
- /// e.Size, e.CompressedSize,
- /// d.ToString("dd-MM-yy"), d.ToString("t"),
- /// e.Name);
- /// } // foreach
- /// } // Main(args)
- /// } // class MainClass
- /// </code>
- /// </example>
- public class ZipFile : IEnumerable, IDisposable
- {
- #region ZipEntryEnumeration Class
- /// <summary>
- /// Zip entry enumeration
- /// </summary>
- private class ZipEntryEnumeration : IEnumerator
- {
- #region Current (Public)
- /// <summary>
- /// Current
- /// </summary>
- /// <returns>Object</returns>
- public object Current
- {
- get
- {
- return array[ptr];
- }
- }
- #endregion
-
- #region Private
-
- #region array (Private)
- /// <summary>
- /// Array
- /// </summary>
- private readonly ZipEntry[] array;
- #endregion
-
- #region ptr (Private)
- /// <summary>
- /// Pointer to entry as an integer
- /// </summary>
- /// <returns>-</returns>
- private int ptr = -1;
- #endregion
-
- #endregion
-
- #region Constructors
- /// <summary>
- /// Create zip entry enumeration
- /// </summary>
- /// <param name="arr">Array</param>
- public ZipEntryEnumeration(ZipEntry[] arr)
- {
- array = arr;
- }
- #endregion
-
- #region IEnumerator Members
- /// <summary>
- /// Move next
- /// </summary>
- /// <returns>True if there is more to enumerate</returns>
- public bool MoveNext()
- {
- return (++ptr < array.Length);
- }
-
- /// <summary>
- /// Reset
- /// </summary>
- public void Reset()
- {
- ptr = -1;
- }
- #endregion
- }
- #endregion
-
- #region PartialInputStream Class
- /// <summary>
- /// Partial input stream
- /// </summary>
- /// <returns>Inflater input stream</returns>
- private class PartialInputStream : InflaterInputStream
- {
- #region IsEntryAvailable (Public)
- /// <summary>
- /// Available
- /// </summary>
- public override int IsEntryAvailable
- {
- get
- {
- long amount = end - filepos;
- if (amount > Int32.MaxValue)
- {
- return Int32.MaxValue;
- }
-
- return (int)amount;
- }
- }
- #endregion
-
- #region Private
-
- #region baseStream (Private)
- /// <summary>
- /// Base stream
- /// </summary>
- private readonly Stream baseStream;
- #endregion
-
- #region end (Private)
- /// <summary>
- /// File position
- /// </summary>
- private readonly long end;
- #endregion
-
- #region filepos (Private)
- /// <summary>
- /// File position
- /// </summary>
- private long filepos;
- #endregion
-
- #endregion
-
- #region Constructors
- /// <summary>
- /// Create partial input stream
- /// </summary>
- /// <param name="baseStream">Base stream</param>
- /// <param name="start">Start</param>
- /// <param name="len">Len</param>
- public PartialInputStream(Stream baseStream, long start, long len)
- : base(baseStream)
- {
- this.baseStream = baseStream;
- filepos = start;
- end = start + len;
- }
- #endregion
-
- #region ReadByte (Public)
- /// <summary>
- /// Read a byte from this stream.
- /// </summary>
- /// <returns>Returns the byte read or -1 on end of stream.</returns>
- public override int ReadByte()
- {
- if (filepos == end)
- {
- return -1; //ok
- }
-
- //why? This produces CA2002: Do not lock on objects with weak identity
- //lock (baseStream)
- //{
- baseStream.Seek(filepos++, SeekOrigin.Begin);
- return baseStream.ReadByte();
- //} // lock
- }
- #endregion
-
- #region Close (Public)
- /// <summary>
- /// Close this partial input stream.
- /// </summary>
- /// <remarks>
- /// The underlying stream is not closed.
- /// Close the parent ZipFile class to do that.
- /// </remarks>
- public override void Close()
- {
- // Do nothing at all!
- }
- #endregion
-
- #region Read (Public)
- /// <summary>
- /// Read
- /// </summary>
- /// <param name="b">B</param>
- /// <param name="off">Off</param>
- /// <param name="len">Len</param>
- /// <returns>Int</returns>
- public override int Read(byte[] b, int off, int len)
- {
- if (len > end - filepos)
- {
- len = (int)(end - filepos);
- if (len == 0)
- {
- return 0;
- }
- }
-
- //why? This produces CA2002: Do not lock on objects with weak identity
- //lock (baseStream)
- //{
- baseStream.Seek(filepos, SeekOrigin.Begin);
- int count = baseStream.Read(b, off, len);
- if (count > 0)
- {
- filepos += len;
- }
- return count;
- //} // lock
- }
- #endregion
-
- #region SkipBytes (Public)
- /// <summary>
- /// Skip bytes
- /// </summary>
- /// <param name="amount">Amount</param>
- /// <returns>Long</returns>
- public long SkipBytes(long amount)
- {
- if (amount < 0)
- {
- throw new ArgumentOutOfRangeException();
- }
- if (amount > end - filepos)
- {
- amount = end - filepos;
- }
- filepos += amount;
- return amount;
- }
- #endregion
- }
- #endregion
-
- #region Delegates
- /// <summary>
- /// Delegate for handling keys/password setting during
- /// compression / decompression.
- /// </summary>
- public delegate void KeysRequiredEventHandler(
- object sender,
- KeysRequiredEventArgs e
- );
- #endregion
-
- #region Open (Static)
- /// <summary>
- /// Opens a zip file with the given name for reading an returns it or
- /// 'null' if the file is invalid.
- /// </summary>
- /// <param name="zipFilePath">Zip file path</param>
- public static ZipFile Open(string zipFilePath)
- {
- if (FileHelper.Exists(zipFilePath) == false)
- {
- Log.Warning("Can't open the zip file '" + zipFilePath +
- "' because it doesn't exists");
- return null;
- } // if
-
- ZipFile zipFile = null;
- try
- {
- FileStream zipFileStream = FileHelper.Open(zipFilePath);
- zipFile = new ZipFile(zipFileStream)
- {
- FilePath = zipFilePath,
- };
-
- zipFile.ReadEntries();
- } // try
- catch (Exception ex)
- {
- Log.Warning("Couldn't open the zip file '" + zipFilePath +
- "' because of reason: '" + ex.Message + "'");
- zipFile.Close();
- zipFile = null;
- } // catch
-
- return zipFile;
- }
- #endregion
-
- #region Create (Static)
- /// <summary>
- /// Creates a zip file with the given name and returns it or 'null' if the
- /// file can't be created for some reason.
- /// </summary>
- /// <param name="newZipFilePath">New zip file path</param>
- /// <param name="overrideIfExists">Override if exists</param>
- /// <returns>Zip file</returns>
- public static ZipFile Create(string newZipFilePath,
- bool overrideIfExists = false)
- {
- FileMode fileCreationMode = FileMode.CreateNew;
-
- if (FileHelper.Exists(newZipFilePath))
- {
- if (overrideIfExists)
- {
- // Switch to "override file" mode
- fileCreationMode = FileMode.Create;
- } // if
- else
- {
- Log.Warning("Can't create zip file '" + newZipFilePath +
- "' because it already exists");
- return null;
- } // else
- } // if
-
- ZipFile zipFile = null;
- try
- {
- FileStream zipFileStream = FileHelper.Open(newZipFilePath,
- fileCreationMode, FileAccess.ReadWrite, FileShare.None);
- zipFile = new ZipFile(zipFileStream)
- {
- FilePath = newZipFilePath,
- };
- zipFile.zipOutput = new ZipOutputStream(zipFile.baseStream);
- // Compression: 0=none - 9=best
- zipFile.zipOutput.SetCompressionLevel(9);
- } // try
- catch (Exception ex)
- {
- Log.Warning("Couldn't create the zip file '" + newZipFilePath +
- "' because of reason: '" + ex.Message + "'");
- } // catch
-
- return zipFile;
- }
- #endregion
-
- #region ZipFileComment (Public)
- /// <summary>
- /// Gets the comment for the zip file.
- /// </summary>
- public string ZipFileComment
- {
- get
- {
- return comment;
- } // get
- }
- #endregion
-
- #region FilePath (Public)
- /// <summary>
- /// The path of this zip file.
- /// </summary>
- public string FilePath
- {
- get;
- private set;
- }
- #endregion
-
- #region Size (Public)
- /// <summary>
- /// Gets the size (in bytes) of this zip file.
- /// </summary>
- /// <exception cref="InvalidOperationException">
- /// The Zip file has been closed.
- /// </exception>
- public long Size
- {
- get
- {
- if (baseStream != null)
- {
- return baseStream.Length;
- } // if
- else
- {
- throw new InvalidOperationException("ZipFile is closed");
- } // else
- } // get
- }
- #endregion
-
- #region Password (Public)
- /// <summary>
- /// Password to be used for encrypting/decrypting files.
- /// </summary>
- /// <remarks>Set to null if no password is required.</remarks>
- public string Password
- {
- set
- {
- if (value == null ||
- value.Length == 0)
- {
- key = null;
- } // if
- else
- {
- key = ZipEncryption.GenerateKeys(Encoding.ASCII.GetBytes(value));
- } // else
- } // set
- }
- #endregion
-
- #region EntryByIndex (Public)
- /// <summary>
- /// Indexer property for ZipEntries
- /// </summary>
- [IndexerName("EntryByIndex")]
- public ZipEntry this[int index]
- {
- get
- {
- return entries[index].Clone();
- } // get
- }
- #endregion
-
- #region zipOutput (Public)
- /// <summary>
- /// Zip output
- /// </summary>
- public ZipOutputStream zipOutput;
- #endregion
-
- #region KeysRequired (Public)
- /// <summary>
- /// Event handler for handling encryption keys.
- /// </summary>
- public KeysRequiredEventHandler KeysRequired;
- #endregion
-
- #region Private
-
- #region baseStream (Private)
- /// <summary>
- /// Base Stream
- /// </summary>
- private readonly Stream baseStream;
- #endregion
-
- #region comment (Private)
- /// <summary>
- /// File Comment
- /// </summary>
- private string comment;
- #endregion
-
- #region isStreamOwner (Private)
- /// <summary>
- /// Get/set a flag indicating if the underlying stream is owned by the
- /// ZipFile instance. If the flag is true then the stream will be closed
- /// when <see cref="Close">Close</see> is called.
- /// </summary>
- /// <remarks>
- /// The default value is true in all cases.
- /// </remarks>
- private bool isStreamOwner = true;
- #endregion
-
- #region offsetOfFirstEntry (Private)
- /// <summary>
- /// Offset of first entry
- /// </summary>
- /// <returns>0</returns>
- private long offsetOfFirstEntry;
- #endregion
-
- #region entries (Private)
- /// <summary>
- /// Entries
- /// </summary>
- private ZipEntry[] entries;
- #endregion
-
- #region key (Private)
- /// <summary>
- /// The encryption key value.
- /// </summary>
- /// <returns>Null</returns>
- private byte[] key;
- #endregion
-
- #region HaveKeys (Private)
- /// <summary>
- /// Have keys
- /// </summary>
- /// <returns>Does this zip file entry have keys?</returns>
- private bool HaveKeys
- {
- get
- {
- return key != null;
- } // get
- }
- #endregion
-
- #endregion
-
- #region Constructors
- /// <summary>
- /// Opens a Zip file reading the given Stream
- /// </summary>
- /// <exception cref="ZipException">
- /// The file doesn't contain a valid zip archive.
- /// <para />
- /// The stream provided cannot seek.
- /// </exception>
- private ZipFile(Stream setStream)
- {
- baseStream = setStream;
- }
- #endregion
-
- #region IDisposable Members
- /// <summary>
- /// Dispose
- /// </summary>
- public void Dispose()
- {
- Close();
- }
- #endregion
-
- #region IEnumerable Members
- /// <summary>
- /// Returns an enumerator for the Zip entries in this Zip file.
- /// </summary>
- /// <exception cref="InvalidOperationException">
- /// The Zip file has been closed.
- /// </exception>
- public IEnumerator GetEnumerator()
- {
- if (entries == null)
- {
- throw new InvalidOperationException("ZipFile has closed");
- }
-
- return new ZipEntryEnumeration(entries);
- }
- #endregion
-
- #region Close (Public)
- /// <summary>
- /// Closes the ZipFile. If the stream is
- /// <see cref="isStreamOwner">owned</see> then this also closes the
- /// underlying input stream. Once closed, no further instance methods
- /// should be called.
- /// </summary>
- /// <exception cref="System.IO.IOException">
- /// An i/o error occurs.
- /// </exception>
- public void Close()
- {
- entries = null;
- if (isStreamOwner)
- {
- /*produces warning CA2002: Do not lock on objects with weak identity
- lock (baseStream)
- {
- baseStream.Close();
- } // lock
- */
-
- if (zipOutput != null)
- {
- zipOutput.Finish();
- zipOutput.Close();
- zipOutput = null;
- } // if (zipOutput)
-
- baseStream.Close();
- }
- }
- #endregion
-
- #region FindEntry (Public)
- /// <summary>
- /// Return the index of the entry with a matching name
- /// </summary>
- /// <param name="searchName">Entry name to find</param>
- /// <param name="ignoreCase">If true the comparison is case insensitive
- /// </param>
- /// <returns>The index position of the matching entry or -1 if not found
- /// </returns>
- /// <exception cref="InvalidOperationException">
- /// The Zip file has been closed.
- /// </exception>
- public int FindEntry(string searchName, bool ignoreCase)
- {
- if (entries == null)
- {
- throw new InvalidOperationException("ZipFile has been closed");
- } // if
-
- for (int index = 0; index < entries.Length; index++)
- {
- if (string.Compare(searchName, entries[index].Name, ignoreCase) == 0)
- {
- return index;
- } // if
- } // for
-
- return MathHelper.InvalidIndex;
- }
- #endregion
-
- #region GetEntry (Public)
- /// <summary>
- /// Searches for a zip entry in this archive with the given name.
- /// String comparisons are case insensitive
- /// </summary>
- /// <param name="searchName">
- /// The name to find. May contain directory components separated by
- /// slashes ('/').
- /// </param>
- /// <returns>
- /// The zip entry, or null if no entry with that name exists.
- /// </returns>
- /// <exception cref="InvalidOperationException">
- /// The Zip file has been closed.
- /// </exception>
- public ZipEntry GetEntry(string searchName)
- {
- if (entries == null)
- {
- throw new InvalidOperationException("ZipFile has been closed");
- } // if
-
- int index = FindEntry(searchName, true);
- return (index >= 0)
- ? entries[index].Clone()
- : null;
- }
- #endregion
-
- #region TestArchive (Public)
- /// <summary>
- /// Test an archive for integrity/validity
- /// </summary>
- /// <param name="testData">Perform low level data Crc check</param>
- /// <returns>true if the test passes, false otherwise</returns>
- public bool TestArchive(bool testData)
- {
- bool result = true;
- try
- {
- for (int index = 0; index < entries.Length; ++index)
- {
- long localOffset = CheckLocalHeader(this[index], true, true);
- if (testData)
- {
- Stream entryStream = GetInputStream(index);
- // Note: Test events for updating info, recording errors etc
- Crc32 crc = new Crc32();
- byte[] buffer = new byte[4096];
- int bytesRead;
- while ((bytesRead =
- entryStream.Read(buffer, 0, buffer.Length)) > 0)
- {
- crc.Update(buffer, 0, bytesRead);
- } // while
-
- if (this[index].Crc != crc.Value)
- {
- result = false;
- Log.Warning(
- "Crc failed, expected " + this[index].Crc +
- ", but calculated " + crc.Value);
- break;
- } // if
- } // if
- } // for
- } // try
- catch
- {
- result = false;
- } // catch
-
- return result;
- }
- #endregion
-
- #region GetInputStream (Public)
- /// <summary>
- /// Creates an input stream reading a zip entry
- /// </summary>
- /// <param name="entryIndex">The index of the entry to obtain an input
- /// stream for.</param>
- /// <returns>
- /// An input stream.
- /// </returns>
- /// <exception cref="InvalidOperationException">
- /// The ZipFile has already been closed
- /// </exception>
- /// <exception cref="Delta.Utilities.Compression.ZipException">
- /// The compression method for the entry is unknown
- /// </exception>
- /// <exception cref="IndexOutOfRangeException">
- /// The entry is not found in the ZipFile
- /// </exception>
- public Stream GetInputStream(int entryIndex)
- {
- if (entries == null)
- {
- throw new InvalidOperationException("ZipFile has closed");
- } // if
-
- long start = CheckLocalHeader(entries[entryIndex]);
- CompressionMethod method = entries[entryIndex].CompressionMethod;
- Stream istr = new PartialInputStream(
- baseStream, start, entries[entryIndex].CompressedSize);
-
- if (entries[entryIndex].IsCrypted)
- {
- istr = CreateAndInitDecryptionStream(istr, entries[entryIndex]);
- if (istr == null)
- {
- throw new ZipException("Unable to decrypt this entry");
- } // if
- } // if
-
- switch (method)
- {
- case CompressionMethod.Stored:
- return istr;
- case CompressionMethod.Deflated:
- return new InflaterInputStream(istr, new Inflater(true));
- default:
- throw new ZipException(
- "Unsupported compression method " + method);
- } // switch
- }
- #endregion
-
- #region Append (Public)
- public void Append(string filePath, string entryName = null)
- {
- if (FileHelper.Exists(filePath) == false)
- {
- throw new FileNotFoundException("Unable to add file '" + filePath +
- "' to zip '" + this +
- "' because the file was not found!");
- }
- if (zipOutput == null)
- {
- throw new InvalidOperationException("ZipFile has closed or was not " +
- "created for adding zip entries!");
- }
-
- if (entryName == null)
- {
- entryName = FileHelper.TryToUseRelativePath(filePath);
- }
-
- try
- {
- Stream fs = new FileStream(filePath, FileMode.Open,
- FileAccess.Read, FileShare.Read);
-
- byte[] buffer = new byte[fs.Length];
- fs.Read(buffer, 0, buffer.Length);
- fs.Close();
-
- ZipEntry newEntry = new ZipEntry(entryName);
- zipOutput.PutNextEntry(newEntry);
- zipOutput.Write(buffer, 0, buffer.Length);
- } // try
- catch (Exception ex)
- {
- Log.Warning("Could not save file '" + filePath +
- "' into zip (entryName=" + entryName + "): " + ex);
- } // catch
- }
- #endregion
-
- #region Methods (Private)
-
- #region OnKeysRequired
- /// <summary>
- /// Handles getting of encryption keys when required.
- /// </summary>
- /// <param name="fileName">The file for which encryptino keys are required.
- /// </param>
- private void OnKeysRequired(string fileName)
- {
- if (KeysRequired != null)
- {
- KeysRequiredEventArgs krea = new KeysRequiredEventArgs(fileName, key);
- KeysRequired(this, krea);
- key = krea.GetKey();
- }
- }
- #endregion
-
- #region ReadEntries
- /// <summary>
- /// Search for and read the central directory of a zip file filling the
- /// entries array. This is called exactly once by the constructors.
- /// </summary>
- /// <exception cref="System.IO.IOException">
- /// An i/o error occurs.
- /// </exception>
- /// <exception cref="Delta.Utilities.Compression.ZipException">
- /// The central directory is malformed or cannot be found
- /// </exception>
- private void ReadEntries()
- {
- // Search for the End Of Central Directory. When a zip comment is
- // present the directory may start earlier.
- //
- // Note: The search is limited to 64K which is the maximum size of a
- // trailing comment field to aid speed. This should be compatible with
- // both SFX and ZIP files but has only been tested for Zip files.
- // Need to confirm this is valid in all cases.
- // Could also speed this up by reading memory in larger blocks.
-
- if (baseStream.CanSeek == false)
- {
- throw new ZipException("ZipFile stream must be seekable");
- } // if
-
- long locatedCentralDirOffset = LocateBlockWithSignature(
- ZipConstants.EndSig, baseStream.Length,
- ZipConstants.EndHeader, 0xffff);
- if (locatedCentralDirOffset < 0)
- {
- throw new ZipException("Cannot find central directory");
- } // if
-
- //unused: int thisDiskNumber =
- ReadLeShort();
- //unused: int startCentralDirDisk =
- ReadLeShort();
- int entriesForThisDisk = ReadLeShort();
- int entriesForWholeCentralDir = ReadLeShort();
- int centralDirSize = ReadLeInt();
- int offsetOfCentralDir = ReadLeInt();
- int commentSize = ReadLeShort();
-
- byte[] zipComment = new byte[commentSize];
- baseStream.Read(zipComment, 0, zipComment.Length);
- comment = ZipConstants.ConvertToString(zipComment);
-
- /* Its seems possible that this is too strict, more digging required.
- if (thisDiskNumber != 0 ||
- startCentralDirDisk != 0 ||
- entriesForThisDisk != entriesForWholeCentralDir)
- {
- throw new ZipException("Spanned archives are not currently handled");
- }
- */
-
- entries = new ZipEntry[entriesForWholeCentralDir];
-
- // SFX support, find the offset of the first entry vis the start of the
- // stream. This applies to Zip files that are appended to the end of the
- // SFX stub. Zip files created by some archivers have the offsets altered
- // to reflect the true offsets and so dont require any adjustment here...
- if (offsetOfCentralDir < locatedCentralDirOffset - (4 + centralDirSize))
- {
- offsetOfFirstEntry = locatedCentralDirOffset -
- (4 + centralDirSize + offsetOfCentralDir);
- if (offsetOfFirstEntry <= 0)
- {
- throw new ZipException("Invalid SFX file");
- } // if
- } // if
-
- baseStream.Seek(offsetOfFirstEntry + offsetOfCentralDir,
- SeekOrigin.Begin);
-
- for (int entryId = 0; entryId < entriesForThisDisk; entryId++)
- {
- if (ReadLeInt() !=
- ZipConstants.CentralDirectorySig)
- {
- throw new ZipException("Wrong Central Directory signature");
- } // if
-
- int hostSystemId = ReadLeShort();
- int entryVersion = ReadLeShort();
- int bitFlags = ReadLeShort();
- int method = ReadLeShort();
- int dostime = ReadLeInt();
- int crc = ReadLeInt();
- int csize = ReadLeInt();
- int size = ReadLeInt();
- int nameLength = ReadLeShort();
- int extraLength = ReadLeShort();
- int commentLength = ReadLeShort();
-
- // Not currently used
- //unused: int diskStartNo =
- ReadLeShort();
- // Not currently used
- //unused: int internalAttributes =
- ReadLeShort();
-
- int externalAttributes = ReadLeInt();
- int offset = ReadLeInt();
-
- byte[] buffer = new byte[Math.Max(nameLength, commentLength)];
-
- baseStream.Read(buffer, 0, nameLength);
- string entryName = ZipConstants.ConvertToString(buffer, nameLength);
-
- ZipEntry entry = new ZipEntry(entryName, entryVersion, hostSystemId)
- {
- CompressionMethod = (CompressionMethod)method,
- Crc = crc & 0xffffffffL,
- Size = size & 0xffffffffL,
- CompressedSize = csize & 0xffffffffL,
- Flags = bitFlags,
- DosTime = (uint)dostime
- };
-
- if (extraLength > 0)
- {
- byte[] extra = new byte[extraLength];
- baseStream.Read(extra, 0, extraLength);
- entry.SetExtraData(extra);
- } // if
-
- if (commentLength > 0)
- {
- baseStream.Read(buffer, 0, commentLength);
- entry.Comment = ZipConstants.ConvertToString(buffer, commentLength);
- } // if
-
- entry.ZipFileIndex = entryId;
- entry.Offset = offset;
- entry.ExternalFileAttributes = externalAttributes;
-
- entries[entryId] = entry;
- } // for
- }
- #endregion
-
- #region ReadLeShort
- /// <summary>
- /// Read an unsigned short in little endian byte order.
- /// </summary>
- /// <returns>Returns the value read.</returns>
- /// <exception cref="IOException">
- /// An i/o error occurs.
- /// </exception>
- /// <exception cref="EndOfStreamException">
- /// The file ends prematurely
- /// </exception>
- private int ReadLeShort()
- {
- return baseStream.ReadByte() | baseStream.ReadByte() << 8;
- }
- #endregion
-
- #region ReadLeInt
- /// <summary>
- /// Read an int in little endian byte order.
- /// </summary>
- /// <returns>Returns the value read.</returns>
- /// <exception cref="IOException">
- /// An i/o error occurs.
- /// </exception>
- /// <exception cref="System.IO.EndOfStreamException">
- /// The file ends prematurely
- /// </exception>
- private int ReadLeInt()
- {
- return ReadLeShort() | ReadLeShort() << 16;
- }
- #endregion
-
- #region LocateBlockWithSignature
- /// <summary>
- /// NOTE this returns the offset of the first byte after the signature.
- /// </summary>
- /// <param name="signature">Signature</param>
- /// <param name="endLocation">End location</param>
- /// <param name="minimumBlockSize">Minimum block size</param>
- /// <param name="maximumVariableData">Maximum variable data</param>
- /// <returns>Long</returns>
- private long LocateBlockWithSignature(int signature, long endLocation,
- int minimumBlockSize, int maximumVariableData)
- {
- long pos = endLocation - minimumBlockSize;
- if (pos < 0)
- {
- return MathHelper.InvalidIndex;
- } // if
-
- long giveUpMarker = Math.Max(pos - maximumVariableData, 0);
-
- // Note: this loop could be optimized for speed.
- do
- {
- if (pos < giveUpMarker)
- {
- return MathHelper.InvalidIndex;
- }
- baseStream.Seek(pos--, SeekOrigin.Begin);
- } while (ReadLeInt() != signature);
-
- return baseStream.Position;
- }
- #endregion
-
- #region CheckLocalHeader
- /// <summary>
- /// Checks, if the local header of the entry at index i matches the
- /// central directory, and returns the offset to the data.
- /// </summary>
- /// <param name="entry">
- /// The entry to test against
- /// </param>
- /// <param name="fullTest">
- /// False by default. If set to true, the check will be extremely picky
- /// about the testing, otherwise be relaxed.
- /// </param>
- /// <param name="extractTest">
- /// Apply extra testing to see if the entry can be extracted by the
- /// library. True by default.
- /// </param>
- /// <returns>
- /// The start offset of the (compressed) data.
- /// </returns>
- /// <exception cref="System.IO.EndOfStreamException">
- /// The stream ends prematurely
- /// </exception>
- /// <exception cref="Delta.Utilities.Compression.ZipException">
- /// The local header signature is invalid, the entry and central header
- /// file name lengths are different or the local and entry compression
- /// methods dont match.
- /// </exception>
- private long CheckLocalHeader(ZipEntry entry, bool fullTest = false,
- bool extractTest = true)
- {
- baseStream.Seek(offsetOfFirstEntry + entry.Offset, SeekOrigin.Begin);
- if (ReadLeInt() !=
- ZipConstants.LocalSignature)
- {
- throw new ZipException(
- "Wrong local header signature");
- } // if
-
- // version required to extract
- short shortValue = (short)ReadLeShort();
- if (extractTest && shortValue > ZipConstants.VersionMadeBy)
- {
- throw new ZipException(
- "Version required to extract this entry not supported (" +
- shortValue + ")");
- } // if
-
- // general purpose bit flags.
- short localFlags = (short)ReadLeShort();
- if (extractTest)
- {
- if ((localFlags &
- (int)
- (GeneralBitFlags.Patched |
- GeneralBitFlags.StrongEncryption |
- GeneralBitFlags.EnhancedCompress |
- GeneralBitFlags.HeaderMasked)) != 0)
- {
- throw new ZipException(
- "The library doesnt support the zip version required to " +
- "extract this entry");
- } // if
- } // if
-
- if (localFlags != entry.Flags)
- {
- throw new ZipException(
- "Central header/local header flags mismatch");
- } // if
-
- if (entry.CompressionMethod !=
- (CompressionMethod)ReadLeShort())
- {
- throw new ZipException(
- "Central header/local header compression method mismatch");
- } // if
-
- // file time
- shortValue = (short)ReadLeShort();
- // file date
- shortValue = (short)ReadLeShort();
-
- // Crc
- int intValue = ReadLeInt();
-
- if (fullTest)
- {
- if ((localFlags &
- (int)GeneralBitFlags.Descriptor) == 0)
- {
- if (intValue != (int)entry.Crc)
- {
- throw new ZipException(
- "Central header/local header crc mismatch");
- }
- } // if
- } // if
-
- // compressed Size
- intValue = ReadLeInt();
- // uncompressed size
- intValue = ReadLeInt();
-
- // Note: make test more correct... can't compare lengths as was done
- // originally as this can fail for MBCS strings. Assuming a code page
- // at this point is not valid? Best is to store the name length in
- // the ZipEntry probably.
- int storedNameLength = ReadLeShort();
- if (entry.Name.Length > storedNameLength)
- {
- throw new ZipException("file name length mismatch");
- } // if
-
- int extraLen = storedNameLength + ReadLeShort();
- return
- offsetOfFirstEntry + entry.Offset + ZipConstants.LocalHeader +
- extraLen;
- }
- #endregion
-
- #region ReadFully
- /// <summary>
- /// Refactor this, its done elsewhere as well
- /// </summary>
- /// <param name="stream">S</param>
- /// <param name="outBuf">Out buf</param>
- private void ReadFully(Stream stream, byte[] outBuf)
- {
- int offset = 0;
- int length = outBuf.Length;
- while (length > 0)
- {
- int count = stream.Read(outBuf, offset, length);
- if (count <= 0)
- {
- throw new ZipException("Unexpected EOF");
- }
- offset += count;
- length -= count;
- }
- }
- #endregion
-
- #region CheckClassicPassword
- /// <summary>
- /// Check classic password
- /// </summary>
- /// <param name="classicCryptoStream">Classic crypto stream</param>
- /// <param name="entry">Entry</param>
- private void CheckClassicPassword(CryptoStream classicCryptoStream,
- ZipEntry entry)
- {
- byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize];
- ReadFully(classicCryptoStream, cryptbuffer);
-
- if ((entry.Flags & (int)GeneralBitFlags.Descriptor) == 0)
- {
- if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] !=
- (byte)(entry.Crc >> 24))
- {
- throw new ZipException("Invalid password");
- } // if
- } // if
- else
- {
- if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] !=
- (byte)((entry.DosTime >> 8) & 0xff))
- {
- throw new ZipException("Invalid password");
- } // if
- } // else
- }
- #endregion
-
- #region CreateAndInitDecryptionStream
- /// <summary>
- /// Create and init decryption stream
- /// </summary>
- /// <param name="setBaseStream">Base stream</param>
- /// <param name="entry">Entry</param>
- /// <returns>Stream</returns>
- private Stream CreateAndInitDecryptionStream(Stream setBaseStream,
- ZipEntry entry)
- {
- CryptoStream result = null;
-
- if (entry.Version < ZipConstants.VersionStrongEncryption ||
- (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0)
- {
- ZipEncryptionManaged classicManaged = new ZipEncryptionManaged();
-
- OnKeysRequired(entry.Name);
- if (HaveKeys == false)
- {
- throw new ZipException(
- "No password available for encrypted stream");
- } // if
-
- result = new CryptoStream(setBaseStream,
- classicManaged.CreateDecryptor(key, null), CryptoStreamMode.Read);
- CheckClassicPassword(result, entry);
- } // if
- else
- {
- throw new ZipException(
- "Decryption method not supported");
- } // else
-
- return result;
- }
- #endregion
-
- #region WriteEncryptionHeader
- /// <summary>
- /// Write encryption header
- /// </summary>
- /// <param name="stream">Stream</param>
- /// <param name="crcValue">Crc value</param>
- private void WriteEncryptionHeader(Stream stream, long crcValue)
- {
- byte[] cryptBuffer = new byte[ZipConstants.CryptoHeaderSize];
- Random rnd = new Random();
- rnd.NextBytes(cryptBuffer);
- cryptBuffer[11] = (byte)(crcValue >> 24);
- stream.Write(cryptBuffer, 0, cryptBuffer.Length);
- }
- #endregion
-
- #region CreateAndInitEncryptionStream
- /// <summary>
- /// Create and init encryption stream
- /// </summary>
- /// <param name="setBaseStream">Base stream</param>
- /// <param name="entry">Entry</param>
- /// <returns>Stream</returns>
- private Stream CreateAndInitEncryptionStream(Stream setBaseStream,
- ZipEntry entry)
- {
- CryptoStream result = null;
- if (entry.Version < ZipConstants.VersionStrongEncryption ||
- (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0)
- {
- ZipEncryptionManaged classicManaged = new ZipEncryptionManaged();
-
- OnKeysRequired(entry.Name);
- if (HaveKeys == false)
- {
- throw new ZipException(
- "No password available for encrypted stream");
- } // if
-
- result = new CryptoStream(setBaseStream,
- classicManaged.CreateEncryptor(key, null), CryptoStreamMode.Write);
- if (entry.Crc < 0 ||
- (entry.Flags & 8) != 0)
- {
- WriteEncryptionHeader(result, entry.DosTime << 16);
- } // if
- else
- {
- WriteEncryptionHeader(result, entry.Crc);
- } // else
- } // if
- return result;
- }
- #endregion
-
- #region GetOutputStream
- /// <summary>
- /// Gets an output stream for the specified <see cref="ZipEntry"/>
- /// </summary>
- /// <param name="entry">The entry to get an output stream for.</param>
- /// <param name="fileName"></param>
- /// <returns>The output stream obtained for the entry.</returns>
- private Stream GetOutputStream(ZipEntry entry, string fileName)
- {
- baseStream.Seek(0, SeekOrigin.End);
- Stream result = File.OpenWrite(fileName);
-
- if (entry.IsCrypted)
- {
- result = CreateAndInitEncryptionStream(result, entry);
- }
-
- switch (entry.CompressionMethod)
- {
- case CompressionMethod.Stored:
- break;
-
- case CompressionMethod.Deflated:
- result = new DeflaterOutputStream(result);
- break;
-
- default:
- throw new ZipException(
- "Unknown compression method " + entry.CompressionMethod);
- } // switch
- return result;
- }
- #endregion
-
- #region GetInputStream
- /// <summary>
- /// Creates an input stream reading the given zip entry as
- /// uncompressed data. Normally zip entry should be an entry
- /// returned by GetEntry().
- /// </summary>
- /// <returns>
- /// the input stream.
- /// </returns>
- /// <exception cref="InvalidOperationException">
- /// The ZipFile has already been closed
- /// </exception>
- /// <exception cref="Delta.Utilities.Compression.ZipException">
- /// The compression method for the entry is unknown
- /// </exception>
- /// <exception cref="IndexOutOfRangeException">
- /// The entry is not found in the ZipFile
- /// </exception>
- private Stream GetInputStream(ZipEntry entry)
- {
- if (entries == null)
- {
- throw new InvalidOperationException("ZipFile has closed");
- } // if
-
- int index = entry.ZipFileIndex;
- if (index < 0 ||
- index >= entries.Length ||
- entries[index].Name != entry.Name)
- {
- index = FindEntry(entry.Name, true);
- if (index < 0)
- {
- throw new IndexOutOfRangeException();
- } // if
- } // if
-
- return GetInputStream(index);
- }
- #endregion
-
- #endregion
- }
- }
-