// ZipStorer, by Jaime Olivares // Website: zipstorer.codeplex.com // Version: 2.35 (March 14, 2010) // Simplified to extract-only by Nikse - August 18, 2010 using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Text; namespace Nikse.SubtitleEdit.Core { /// /// Zip archive decompression. Represents a Zip file. /// public class ZipExtractor : IDisposable { /// /// Compression method enumeration /// public enum Compression : ushort { /// Uncompressed storage Store = 0, /// Deflate compression method Deflate = 8 } /// /// Represents an entry in Zip file directory /// public struct ZipFileEntry { /// Compression method public Compression Method; /// Full path and filename as stored in Zip public string FilenameInZip; /// Original file size public uint FileSize; /// Compressed file size public uint CompressedSize; /// Offset of header information inside Zip storage public uint HeaderOffset; /// Offset of file inside Zip storage public uint FileOffset; /// Size of header information public uint HeaderSize; /// 32-bit checksum of entire file public uint Crc32; /// Last modification time of file public DateTime ModifyTime; /// User comment for file public string Comment; /// True if UTF8 encoding for filename and comments, false if default (CP 437) public bool EncodeUTF8; /// Overriden method /// Filename in Zip public override string ToString() { return this.FilenameInZip; } } #region Public fields /// True if UTF8 encoding for filename and comments, false if default (CP 437) public bool EncodeUTF8 = false; /// Force deflate algotithm even if it inflates the stored file. Off by default. public bool ForceDeflating = false; #endregion Public fields #region Private fields // Stream object of storage file private Stream ZipFileStream; // Central dir image private byte[] CentralDirImage = null; // Static CRC32 Table private static UInt32[] CrcTable = null; // Default filename encoder private static Encoding DefaultEncoding = Encoding.GetEncoding(437); #endregion Private fields #region Public methods // Static constructor. Just invoked once in order to create the CRC32 lookup table. static ZipExtractor() { // Generate CRC32 table CrcTable = new UInt32[256]; for (int i = 0; i < CrcTable.Length; i++) { UInt32 c = (UInt32)i; for (int j = 0; j < 8; j++) { if ((c & 1) != 0) c = 3988292384 ^ (c >> 1); else c >>= 1; } CrcTable[i] = c; } } /// /// Method to open an existing storage file /// /// Full path of Zip file to open /// A valid ZipStorer object public static ZipExtractor Open(string filename) { Stream stream = new FileStream(filename, FileMode.Open, FileAccess.Read); ZipExtractor zip = Open(stream); return zip; } /// /// Method to open an existing storage from stream /// /// Already opened stream with zip contents /// A valid ZipStorer object public static ZipExtractor Open(Stream stream) { ZipExtractor zip = new ZipExtractor(); zip.ZipFileStream = stream; if (zip.ReadFileInfo()) return zip; throw new System.IO.InvalidDataException(); } /// /// Close the Zip storage /// /// This is a required step, unless automatic dispose is used public void Close() { if (this.ZipFileStream != null) { this.ZipFileStream.Flush(); this.ZipFileStream.Dispose(); this.ZipFileStream = null; } } /// /// Read all the file records in the central directory /// /// List of all entries in directory public List ReadCentralDir() { if (this.CentralDirImage == null) throw new InvalidOperationException("Central directory currently does not exist"); List result = new List(); for (int pointer = 0; pointer < this.CentralDirImage.Length;) { uint signature = BitConverter.ToUInt32(CentralDirImage, pointer); if (signature != 0x02014b50) break; bool encodeUTF8 = (BitConverter.ToUInt16(CentralDirImage, pointer + 8) & 0x0800) != 0; ushort method = BitConverter.ToUInt16(CentralDirImage, pointer + 10); uint modifyTime = BitConverter.ToUInt32(CentralDirImage, pointer + 12); uint crc32 = BitConverter.ToUInt32(CentralDirImage, pointer + 16); uint comprSize = BitConverter.ToUInt32(CentralDirImage, pointer + 20); uint fileSize = BitConverter.ToUInt32(CentralDirImage, pointer + 24); ushort filenameSize = BitConverter.ToUInt16(CentralDirImage, pointer + 28); ushort extraSize = BitConverter.ToUInt16(CentralDirImage, pointer + 30); ushort commentSize = BitConverter.ToUInt16(CentralDirImage, pointer + 32); uint headerOffset = BitConverter.ToUInt32(CentralDirImage, pointer + 42); uint headerSize = (uint)(46 + filenameSize + extraSize + commentSize); Encoding encoder = encodeUTF8 ? Encoding.UTF8 : DefaultEncoding; ZipFileEntry zfe = new ZipFileEntry(); zfe.Method = (Compression)method; zfe.FilenameInZip = encoder.GetString(CentralDirImage, pointer + 46, filenameSize); zfe.FileOffset = GetFileOffset(headerOffset); zfe.FileSize = fileSize; zfe.CompressedSize = comprSize; zfe.HeaderOffset = headerOffset; zfe.HeaderSize = headerSize; zfe.Crc32 = crc32; zfe.ModifyTime = DosTimeToDateTime(modifyTime); if (commentSize > 0) zfe.Comment = encoder.GetString(CentralDirImage, pointer + 46 + filenameSize + extraSize, commentSize); result.Add(zfe); pointer += (46 + filenameSize + extraSize + commentSize); } return result; } /// /// Copy the contents of a stored file into a physical file /// /// Entry information of file to extract /// Name of file to store uncompressed data /// True if success, false if not. /// Unique compression methods are Store and Deflate public bool ExtractFile(ZipFileEntry zfe, string filename) { // Make sure the parent directory exist string path = System.IO.Path.GetDirectoryName(filename); if (!Directory.Exists(path)) Directory.CreateDirectory(path); // Check it is directory. If so, do nothing if (Directory.Exists(filename)) return true; bool result = false; using (Stream output = new FileStream(filename, FileMode.Create, FileAccess.Write)) { result = ExtractFile(zfe, output); } File.SetCreationTime(filename, zfe.ModifyTime); File.SetLastWriteTime(filename, zfe.ModifyTime); return result; } /// /// Copy the contents of a stored file into an opened stream /// /// Entry information of file to extract /// Stream to store the uncompressed data /// True if success, false if not. /// Unique compression methods are Store and Deflate public bool ExtractFile(ZipFileEntry zfe, Stream stream) { if (!stream.CanWrite) throw new InvalidOperationException("Stream cannot be written"); // check signature byte[] signature = new byte[4]; this.ZipFileStream.Seek(zfe.HeaderOffset, SeekOrigin.Begin); this.ZipFileStream.Read(signature, 0, 4); if (BitConverter.ToUInt32(signature, 0) != 0x04034b50) return false; // Select input stream for inflating or just reading Stream inStream; if (zfe.Method == Compression.Store) inStream = this.ZipFileStream; else if (zfe.Method == Compression.Deflate) inStream = new DeflateStream(this.ZipFileStream, CompressionMode.Decompress, true); else return false; // Buffered copy byte[] buffer = new byte[16384]; this.ZipFileStream.Seek(zfe.FileOffset, SeekOrigin.Begin); uint bytesPending = zfe.FileSize; while (bytesPending > 0) { int bytesRead = inStream.Read(buffer, 0, (int)Math.Min(bytesPending, buffer.Length)); stream.Write(buffer, 0, bytesRead); bytesPending -= (uint)bytesRead; } stream.Flush(); if (zfe.Method == Compression.Deflate) inStream.Dispose(); return true; } #endregion Public methods #region Private methods /// /// Calculate the file offset by reading the corresponding local header /// private uint GetFileOffset(uint headerOffset) { byte[] buffer = new byte[2]; this.ZipFileStream.Seek(headerOffset + 26, SeekOrigin.Begin); this.ZipFileStream.Read(buffer, 0, 2); ushort filenameSize = BitConverter.ToUInt16(buffer, 0); this.ZipFileStream.Read(buffer, 0, 2); ushort extraSize = BitConverter.ToUInt16(buffer, 0); return (uint)(30 + filenameSize + extraSize + headerOffset); } /* Local file header: local file header signature 4 bytes (0x04034b50) version needed to extract 2 bytes general purpose bit flag 2 bytes compression method 2 bytes last mod file time 2 bytes last mod file date 2 bytes crc-32 4 bytes compressed size 4 bytes uncompressed size 4 bytes filename length 2 bytes extra field length 2 bytes filename (variable size) extra field (variable size) */ /* Central directory's File header: central file header signature 4 bytes (0x02014b50) version made by 2 bytes version needed to extract 2 bytes general purpose bit flag 2 bytes compression method 2 bytes last mod file time 2 bytes last mod file date 2 bytes crc-32 4 bytes compressed size 4 bytes uncompressed size 4 bytes filename length 2 bytes extra field length 2 bytes file comment length 2 bytes disk number start 2 bytes internal file attributes 2 bytes external file attributes 4 bytes relative offset of local header 4 bytes filename (variable size) extra field (variable size) file comment (variable size) * /* End of central dir record: end of central dir signature 4 bytes (0x06054b50) number of this disk 2 bytes number of the disk with the start of the central directory 2 bytes total number of entries in the central dir on this disk 2 bytes total number of entries in the central dir 2 bytes size of the central directory 4 bytes offset of start of central directory with respect to the starting disk number 4 bytes zipfile comment length 2 bytes zipfile comment (variable size) */ /* DOS Date and time: MS-DOS date. The date is a packed value with the following format. Bits Description 0-4 Day of the month (1–31) 5-8 Month (1 = January, 2 = February, and so on) 9-15 Year offset from 1980 (add 1980 to get actual year) MS-DOS time. The time is a packed value with the following format. Bits Description 0-4 Second divided by 2 5-10 Minute (0–59) 11-15 Hour (0–23 on a 24-hour clock) */ private static DateTime DosTimeToDateTime(uint dt) { return new DateTime( (int)(dt >> 25) + 1980, (int)(dt >> 21) & 15, (int)(dt >> 16) & 31, (int)(dt >> 11) & 31, (int)(dt >> 5) & 63, (int)(dt & 31) * 2); } // Reads the end-of-central-directory record private bool ReadFileInfo() { if (this.ZipFileStream.Length < 22) return false; try { this.ZipFileStream.Seek(-17, SeekOrigin.End); BinaryReader br = new BinaryReader(this.ZipFileStream); do { this.ZipFileStream.Seek(-5, SeekOrigin.Current); UInt32 sig = br.ReadUInt32(); if (sig == 0x06054b50) { this.ZipFileStream.Seek(6, SeekOrigin.Current); UInt16 entries = br.ReadUInt16(); Int32 centralSize = br.ReadInt32(); UInt32 centralDirOffset = br.ReadUInt32(); UInt16 commentSize = br.ReadUInt16(); // check if comment field is the very last data in file if (this.ZipFileStream.Position + commentSize != this.ZipFileStream.Length) return false; // Copy entire central directory to a memory buffer this.CentralDirImage = new byte[centralSize]; this.ZipFileStream.Seek(centralDirOffset, SeekOrigin.Begin); this.ZipFileStream.Read(this.CentralDirImage, 0, centralSize); // Leave the pointer at the begining of central dir, to append new files this.ZipFileStream.Seek(centralDirOffset, SeekOrigin.Begin); return true; } } while (this.ZipFileStream.Position > 0); } catch { } return false; } #endregion Private methods #region IDisposable Members public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) { this.Close(); } } #endregion IDisposable Members } }