// ZipStorer, by Jaime Olivares // Website: http://github.com/jaime-olivares/zipstorer // Version: 3.3.0 (April 19, 2017) using System.Collections.Generic; using System.Text; #if NET45 using System.Threading.Tasks; #endif namespace System.IO.Compression { #if NETSTANDARD /// /// Extension method for covering missing Close() method in .Net Standard /// public static class StreamExtension { public static void Close(this Stream stream) { stream.Dispose(); GC.SuppressFinalize(stream); } } #endif /// /// Unique class for compression/decompression file. Represents a Zip file. /// public class ZipStorer : 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 #region Private fields // List of files to store private List Files = new List(); // Filename of storage file private string FileName; // Stream object of storage file private Stream ZipFileStream; // General comment private string Comment = ""; // Central dir image private byte[] CentralDirImage = null; // Existing files in zip private ushort ExistingFiles = 0; // File access for Open method private FileAccess Access; // Static CRC32 Table private static UInt32[] CrcTable = null; // Default filename encoder private static Encoding DefaultEncoding = Encoding.GetEncoding(936); #endregion #region Public methods // Static constructor. Just invoked once in order to create the CRC32 lookup table. static ZipStorer() { // 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 create a new storage file /// /// Full path of Zip file to create /// General comment for Zip file /// A valid ZipStorer object public static ZipStorer Create(string _filename, string _comment) { Stream stream = new FileStream(_filename, FileMode.Create, FileAccess.ReadWrite); ZipStorer zip = Create(stream, _comment); zip.Comment = _comment; zip.FileName = _filename; return zip; } /// /// Method to create a new zip storage in a stream /// /// /// /// A valid ZipStorer object public static ZipStorer Create(Stream _stream, string _comment) { ZipStorer zip = new ZipStorer(); zip.Comment = _comment; zip.ZipFileStream = _stream; zip.Access = FileAccess.Write; return zip; } /// /// Method to open an existing storage file /// /// Full path of Zip file to open /// File access mode as used in FileStream constructor /// A valid ZipStorer object public static ZipStorer Open(string _filename, FileAccess _access) { Stream stream = (Stream)new FileStream(_filename, FileMode.Open, _access == FileAccess.Read ? FileAccess.Read : FileAccess.ReadWrite); ZipStorer zip = Open(stream, _access); zip.FileName = _filename; return zip; } /// /// Method to open an existing storage from stream /// /// Already opened stream with zip contents /// File access mode for stream operations /// A valid ZipStorer object public static ZipStorer Open(Stream _stream, FileAccess _access) { if (!_stream.CanSeek && _access != FileAccess.Read) throw new InvalidOperationException("Stream cannot seek"); ZipStorer zip = new ZipStorer(); //zip.FileName = _filename; zip.ZipFileStream = _stream; zip.Access = _access; if (zip.ReadFileInfo()) return zip; throw new System.IO.InvalidDataException(); } /// /// Add full contents of a file into the Zip storage /// /// Compression method /// Full path of file to add to Zip storage /// Filename and path as desired in Zip directory /// Comment for stored file public void AddFile(Compression _method, string _pathname, string _filenameInZip, string _comment) { if (Access == FileAccess.Read) throw new InvalidOperationException("Writing is not alowed"); using (var stream = new FileStream(_pathname, FileMode.Open, FileAccess.Read)) { AddStream(_method, _filenameInZip, stream, File.GetLastWriteTime(_pathname), _comment); } } /// /// Add full contents of a stream into the Zip storage /// /// Compression method /// Filename and path as desired in Zip directory /// Stream object containing the data to store in Zip /// Modification time of the data to store /// Comment for stored file public void AddStream(Compression _method, string _filenameInZip, Stream _source, DateTime _modTime, string _comment) { if (Access == FileAccess.Read) throw new InvalidOperationException("Writing is not alowed"); // Prepare the fileinfo ZipFileEntry zfe = new ZipFileEntry(); zfe.Method = _method; zfe.EncodeUTF8 = this.EncodeUTF8; zfe.FilenameInZip = NormalizedFilename(_filenameInZip); zfe.Comment = _comment ?? ""; // Even though we write the header now, it will have to be rewritten, since we don't know compressed size or crc. zfe.Crc32 = 0; // to be updated later zfe.HeaderOffset = (uint)this.ZipFileStream.Position; // offset within file of the start of this local record zfe.ModifyTime = _modTime; // Write local header WriteLocalHeader(ref zfe); zfe.FileOffset = (uint)this.ZipFileStream.Position; // Write file to zip (store) Store(ref zfe, _source); _source.Close(); this.UpdateCrcAndSizes(ref zfe); Files.Add(zfe); } /// /// Updates central directory (if pertinent) and close the Zip storage /// /// This is a required step, unless automatic dispose is used public void Close() { if (this.Access != FileAccess.Read) { uint centralOffset = (uint)this.ZipFileStream.Position; uint centralSize = 0; if (this.CentralDirImage != null) this.ZipFileStream.Write(CentralDirImage, 0, CentralDirImage.Length); for (int i = 0; i < Files.Count; i++) { long pos = this.ZipFileStream.Position; this.WriteCentralDirRecord(Files[i]); centralSize += (uint)(this.ZipFileStream.Position - pos); } if (this.CentralDirImage != null) this.WriteEndRecord(centralSize + (uint)CentralDirImage.Length, centralOffset); else this.WriteEndRecord(centralSize, centralOffset); } 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) ?? DateTime.Now; 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 = 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; using(var output = new FileStream(_filename, FileMode.Create, FileAccess.Write)) { result = ExtractFile(_zfe, output); } if (result) { 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; } #if NET45 /// /// 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 async Task ExtractFileAsync(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); await this.ZipFileStream.ReadAsync(signature, 0, 4).ConfigureAwait(false); 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 = await inStream.ReadAsync(buffer, 0, (int)Math.Min(bytesPending, buffer.Length)).ConfigureAwait(false); await _stream.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false); bytesPending -= (uint)bytesRead; } _stream.Flush(); if (_zfe.Method == Compression.Deflate) inStream.Dispose(); return true; } #endif /// /// Copy the contents of a stored file into a byte array /// /// Entry information of file to extract /// Byte array with uncompressed data /// True if success, false if not. /// Unique compression methods are Store and Deflate public bool ExtractFile(ZipFileEntry _zfe, out byte[] _file) { using (MemoryStream ms = new MemoryStream()) { if (ExtractFile(_zfe, ms)) { _file = ms.ToArray(); return true; } else { _file = null; return false; } } } /// /// Removes one of many files in storage. It creates a new Zip file. /// /// Reference to the current Zip object /// List of Entries to remove from storage /// True if success, false if not /// This method only works for storage of type FileStream public static bool RemoveEntries(ref ZipStorer _zip, List _zfes) { if (!(_zip.ZipFileStream is FileStream)) throw new InvalidOperationException("RemoveEntries is allowed just over streams of type FileStream"); //Get full list of entries var fullList = _zip.ReadCentralDir(); //In order to delete we need to create a copy of the zip file excluding the selected items var tempZipName = Path.GetTempFileName(); var tempEntryName = Path.GetTempFileName(); try { var tempZip = ZipStorer.Create(tempZipName, string.Empty); foreach (ZipFileEntry zfe in fullList) { if (!_zfes.Contains(zfe)) { if (_zip.ExtractFile(zfe, tempEntryName)) { tempZip.AddFile(zfe.Method, tempEntryName, zfe.FilenameInZip, zfe.Comment); } } } _zip.Close(); tempZip.Close(); File.Delete(_zip.FileName); File.Move(tempZipName, _zip.FileName); _zip = ZipStorer.Open(_zip.FileName, _zip.Access); } catch { return false; } finally { if (File.Exists(tempZipName)) File.Delete(tempZipName); if (File.Exists(tempEntryName)) File.Delete(tempEntryName); } return true; } #endregion #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) */ private void WriteLocalHeader(ref ZipFileEntry _zfe) { long pos = this.ZipFileStream.Position; Encoding encoder = _zfe.EncodeUTF8 ? Encoding.UTF8 : DefaultEncoding; byte[] encodedFilename = encoder.GetBytes(_zfe.FilenameInZip); this.ZipFileStream.Write(new byte[] { 80, 75, 3, 4, 20, 0}, 0, 6); // No extra header this.ZipFileStream.Write(BitConverter.GetBytes((ushort)(_zfe.EncodeUTF8 ? 0x0800 : 0)), 0, 2); // filename and comment encoding this.ZipFileStream.Write(BitConverter.GetBytes((ushort)_zfe.Method), 0, 2); // zipping method this.ZipFileStream.Write(BitConverter.GetBytes(DateTimeToDosTime(_zfe.ModifyTime)), 0, 4); // zipping date and time this.ZipFileStream.Write(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, 0, 12); // unused CRC, un/compressed size, updated later this.ZipFileStream.Write(BitConverter.GetBytes((ushort)encodedFilename.Length), 0, 2); // filename length this.ZipFileStream.Write(BitConverter.GetBytes((ushort)0), 0, 2); // extra length this.ZipFileStream.Write(encodedFilename, 0, encodedFilename.Length); _zfe.HeaderSize = (uint)(this.ZipFileStream.Position - pos); } /* 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) */ private void WriteCentralDirRecord(ZipFileEntry _zfe) { Encoding encoder = _zfe.EncodeUTF8 ? Encoding.UTF8 : DefaultEncoding; byte[] encodedFilename = encoder.GetBytes(_zfe.FilenameInZip); byte[] encodedComment = encoder.GetBytes(_zfe.Comment); this.ZipFileStream.Write(new byte[] { 80, 75, 1, 2, 23, 0xB, 20, 0 }, 0, 8); this.ZipFileStream.Write(BitConverter.GetBytes((ushort)(_zfe.EncodeUTF8 ? 0x0800 : 0)), 0, 2); // filename and comment encoding this.ZipFileStream.Write(BitConverter.GetBytes((ushort)_zfe.Method), 0, 2); // zipping method this.ZipFileStream.Write(BitConverter.GetBytes(DateTimeToDosTime(_zfe.ModifyTime)), 0, 4); // zipping date and time this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.Crc32), 0, 4); // file CRC this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.CompressedSize), 0, 4); // compressed file size this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.FileSize), 0, 4); // uncompressed file size this.ZipFileStream.Write(BitConverter.GetBytes((ushort)encodedFilename.Length), 0, 2); // Filename in zip this.ZipFileStream.Write(BitConverter.GetBytes((ushort)0), 0, 2); // extra length this.ZipFileStream.Write(BitConverter.GetBytes((ushort)encodedComment.Length), 0, 2); this.ZipFileStream.Write(BitConverter.GetBytes((ushort)0), 0, 2); // disk=0 this.ZipFileStream.Write(BitConverter.GetBytes((ushort)0), 0, 2); // file type: binary this.ZipFileStream.Write(BitConverter.GetBytes((ushort)0), 0, 2); // Internal file attributes this.ZipFileStream.Write(BitConverter.GetBytes((ushort)0x8100), 0, 2); // External file attributes (normal/readable) this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.HeaderOffset), 0, 4); // Offset of header this.ZipFileStream.Write(encodedFilename, 0, encodedFilename.Length); this.ZipFileStream.Write(encodedComment, 0, encodedComment.Length); } /* 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) */ private void WriteEndRecord(uint _size, uint _offset) { Encoding encoder = this.EncodeUTF8 ? Encoding.UTF8 : DefaultEncoding; byte[] encodedComment = encoder.GetBytes(this.Comment); this.ZipFileStream.Write(new byte[] { 80, 75, 5, 6, 0, 0, 0, 0 }, 0, 8); this.ZipFileStream.Write(BitConverter.GetBytes((ushort)Files.Count+ExistingFiles), 0, 2); this.ZipFileStream.Write(BitConverter.GetBytes((ushort)Files.Count+ExistingFiles), 0, 2); this.ZipFileStream.Write(BitConverter.GetBytes(_size), 0, 4); this.ZipFileStream.Write(BitConverter.GetBytes(_offset), 0, 4); this.ZipFileStream.Write(BitConverter.GetBytes((ushort)encodedComment.Length), 0, 2); this.ZipFileStream.Write(encodedComment, 0, encodedComment.Length); } // Copies all source file into storage file private void Store(ref ZipFileEntry _zfe, Stream _source) { byte[] buffer = new byte[16384]; int bytesRead; uint totalRead = 0; Stream outStream; long posStart = this.ZipFileStream.Position; long sourceStart = _source.CanSeek ? _source.Position : 0; if (_zfe.Method == Compression.Store) outStream = this.ZipFileStream; else outStream = new DeflateStream(this.ZipFileStream, CompressionMode.Compress, true); _zfe.Crc32 = 0 ^ 0xffffffff; do { bytesRead = _source.Read(buffer, 0, buffer.Length); totalRead += (uint)bytesRead; if (bytesRead > 0) { outStream.Write(buffer, 0, bytesRead); for (uint i = 0; i < bytesRead; i++) { _zfe.Crc32 = ZipStorer.CrcTable[(_zfe.Crc32 ^ buffer[i]) & 0xFF] ^ (_zfe.Crc32 >> 8); } } } while (bytesRead > 0); outStream.Flush(); if (_zfe.Method == Compression.Deflate) outStream.Dispose(); _zfe.Crc32 ^= 0xffffffff; _zfe.FileSize = totalRead; _zfe.CompressedSize = (uint)(this.ZipFileStream.Position - posStart); // Verify for real compression if (_zfe.Method == Compression.Deflate && !this.ForceDeflating && _source.CanSeek && _zfe.CompressedSize > _zfe.FileSize) { // Start operation again with Store algorithm _zfe.Method = Compression.Store; this.ZipFileStream.Position = posStart; this.ZipFileStream.SetLength(posStart); _source.Position = sourceStart; this.Store(ref _zfe, _source); } } /* 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 uint DateTimeToDosTime(DateTime _dt) { return (uint)( (_dt.Second / 2) | (_dt.Minute << 5) | (_dt.Hour << 11) | (_dt.Day<<16) | (_dt.Month << 21) | ((_dt.Year - 1980) << 25)); } private DateTime? DosTimeToDateTime(uint _dt) { int year = (int)(_dt >> 25) + 1980; int month = (int)(_dt >> 21) & 15; int day = (int)(_dt >> 16) & 31; int hours = (int)(_dt >> 11) & 31; int minutes = (int)(_dt >> 5) & 63; int seconds = (int)(_dt & 31) * 2; if (month==0 || day == 0) return null; return new DateTime(year, month, day, hours, minutes, seconds); } /* CRC32 algorithm The 'magic number' for the CRC is 0xdebb20e3. The proper CRC pre and post conditioning is used, meaning that the CRC register is pre-conditioned with all ones (a starting value of 0xffffffff) and the value is post-conditioned by taking the one's complement of the CRC residual. If bit 3 of the general purpose flag is set, this field is set to zero in the local header and the correct value is put in the data descriptor and in the central directory. */ private void UpdateCrcAndSizes(ref ZipFileEntry _zfe) { long lastPos = this.ZipFileStream.Position; // remember position this.ZipFileStream.Position = _zfe.HeaderOffset + 8; this.ZipFileStream.Write(BitConverter.GetBytes((ushort)_zfe.Method), 0, 2); // zipping method this.ZipFileStream.Position = _zfe.HeaderOffset + 14; this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.Crc32), 0, 4); // Update CRC this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.CompressedSize), 0, 4); // Compressed size this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.FileSize), 0, 4); // Uncompressed size this.ZipFileStream.Position = lastPos; // restore position } // Replaces backslashes with slashes to store in zip header private string NormalizedFilename(string _filename) { string filename = _filename.Replace('\\', '/'); int pos = filename.IndexOf(':'); if (pos >= 0) filename = filename.Remove(0, pos + 1); return filename.Trim('/'); } // 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.ExistingFiles = entries; 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 #region IDisposable Members /// /// Closes the Zip file stream /// public void Dispose() { this.Close(); } #endregion } }