Click here to Skip to main content
15,891,136 members
Articles / Web Development / ASP.NET

SimpleZip

Rate me:
Please Sign up or sign in to vote.
4.67/5 (14 votes)
25 Mar 2008CPOL4 min read 48.7K   374   68  
Generate Zip archives without third-party support
// Copyright (c) 2008 Blue Onion Software
// All rights reserved

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Text;

namespace BlueOnionSoftware.SimpleZip
{
    static class SimpleZip
    {
        public static void ZipTo(IEnumerable<string> fileNames, Stream archiveStream)
        {
            if (fileNames == null)
            {
                throw new ArgumentNullException("fileNames");
            }

            if (archiveStream == null)
            {
                throw new ArgumentNullException("archiveStream");
            }

            if (archiveStream.CanWrite == false)
            {
                throw new ArgumentException("archiveStream is not writable");
            }

            var compressedFiles = new List<CompressedFile>();

            using (var counterStream = new CounterStream(archiveStream, true))
            {
                foreach (string file in fileNames)
                {
                    CompressedFile compressedFile = new CompressedFile(file, counterStream.Position);
                    compressedFile.EmitFileHeader(counterStream);
                    compressedFile.EmitFile(counterStream);
                    compressedFiles.Add(compressedFile);
                }

                long centralDirectoryOffset = counterStream.Position;

                foreach (CompressedFile compressedFile in compressedFiles)
                {
                    compressedFile.EmitCatalogFileHeader(counterStream);
                }

                long centralDirectorySize = counterStream.Position - centralDirectoryOffset;

                Write((uint)0x06054b50, counterStream);                 // End of central directory
                Write((ushort)0, counterStream);                        // number of this disk
                Write((ushort)0, counterStream);                        // disk number of central directory
                Write((ushort)compressedFiles.Count, counterStream);    // total # of files on this disk
                Write((ushort)compressedFiles.Count, counterStream);    // total # of files in central directory
                Write((uint)centralDirectorySize, counterStream);
                Write((uint)centralDirectoryOffset, counterStream);
                Write((ushort)0, counterStream);                        // Zip file comment length
                counterStream.Flush();
            }
        }

        // -------------------------------------------------------------------------------------------------

        class CompressedFile
        {
            string FileName { get; set; }
            int RelativeOffset { get; set; }
            ushort GeneralPurpose { get; set; }
            ushort StoreMethod { get; set; }
            uint DosDateTime { get; set; }
            uint Checksum { get; set; }
            int CompressedLength { get; set; }
            int UncompressedLength { get; set; }
            string Name { get; set; }

            public CompressedFile(string fileName, long relativeOffset)
            {
                FileName = fileName;
                Name = NormalizeFileName(fileName);
                RelativeOffset = (int)relativeOffset;
                FileInfo fileInfo = new FileInfo(FileName);
                GeneralPurpose = 8;
                DosDateTime = GetDosDateTime(fileInfo.LastWriteTime);
                StoreMethod = (ushort)((fileInfo.Length < 50) ? 0 : 8);
            }

            public void EmitFileHeader(Stream stream)
            {
                GeneralPurpose = stream.CanSeek ? (ushort)0 : (ushort)8;

                Write((uint)0x04034b50, stream);            // signature
                Write((ushort)0x14, stream);                // version made by
                Write(GeneralPurpose, stream);              // general purpose
                Write(StoreMethod, stream);                 // store method
                Write(DosDateTime, stream);                 // Dos date, time
                Write(Checksum, stream);                    // CRC-32
                Write((uint)CompressedLength, stream);      // compressed size
                Write((uint)UncompressedLength, stream);    // uncompressed size
                Write((ushort)Name.Length, stream);         // file name length
                Write((ushort)0, stream);                   // extra field length
                Write(Encoding.ASCII.GetBytes(Name), stream);
                stream.Flush();
            }

            public void EmitFile(Stream stream)
            {
                using (var fileStream = File.OpenRead(FileName))
                {
                    switch (StoreMethod)
                    {
                        case 0:
                            StoreFile(stream, fileStream);
                            break;

                        case 8:
                            DeflateFile(stream, fileStream);
                            break;
                    }
                }

                Write(Checksum, stream);
                Write((uint)CompressedLength, stream);
                Write((uint)UncompressedLength, stream);
            }

            void StoreFile(Stream archiveStream, Stream sourceStream)
            {
                int count = 1;
                byte[] buffer = new byte[1024];
                uint crc = Crc32.BeginChecksum();

                while (count != 0)
                {
                    count = sourceStream.Read(buffer, 0, buffer.Length);
                    UncompressedLength += count;
                    crc = Crc32.UpdateChecksum(crc, buffer, 0, count);
                    archiveStream.Write(buffer, 0, count);
                }

                Checksum = Crc32.EndChecksum(crc);
                CompressedLength = UncompressedLength;
            }

            void DeflateFile(Stream archiveStream, Stream sourceStream)
            {
                using (var counterStream = new CounterStream(archiveStream, true))
                {
                    using (var deflateStream = new DeflateStream(counterStream, CompressionMode.Compress, true))
                    {
                        int count = 1;
                        byte[] buffer = new byte[1024];
                        uint crc = Crc32.BeginChecksum();

                        while (count != 0)
                        {
                            count = sourceStream.Read(buffer, 0, buffer.Length);
                            UncompressedLength += count;
                            crc = Crc32.UpdateChecksum(crc, buffer, 0, count);
                            deflateStream.Write(buffer, 0, count);
                        }

                        Checksum = Crc32.EndChecksum(crc);
                    }

                    CompressedLength = (int)counterStream.Position;
                }
            }

            public void EmitCatalogFileHeader(Stream stream)
            {
                Write((uint)0x02014b50, stream);            // central directory
                Write((ushort)0x14, stream);                // version made by
                Write((ushort)0x14, stream);                // version required
                Write(GeneralPurpose, stream);              // general purpose
                Write(StoreMethod, stream);                 // store method
                Write(DosDateTime, stream);                 // Dos date, time
                Write(Checksum, stream);                    // CRC-32
                Write((uint)CompressedLength, stream);      // compressed size
                Write((uint)UncompressedLength, stream);    // uncompressed size
                Write((ushort)Name.Length, stream);         // file name length
                Write((ushort)0, stream);                   // extra field length
                Write((ushort)0, stream);                   // file comment length
                Write((ushort)0, stream);                   // disk number start
                Write((ushort)0, stream);                   // internal file attributes
                Write((uint)0x00000020, stream);            // external file attributes
                Write((uint)RelativeOffset, stream);        // relative offset of local header
                Write(Encoding.ASCII.GetBytes(Name), stream);
                stream.Flush();
            }
        }

        // -------------------------------------------------------------------------------------------------

        static class Crc32
        {
            readonly static uint[] table = new uint[] {
			0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
			0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
			0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
			0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
			0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
			0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
			0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
			0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
			0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
			0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
			0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940,
			0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
			0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116,
			0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
			0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
			0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,

			0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A,
			0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
			0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818,
			0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
			0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
			0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
			0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C,
			0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
			0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
			0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
			0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
			0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
			0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086,
			0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
			0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4,
			0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,

			0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
			0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
			0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
			0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
			0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE,
			0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
			0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
			0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
			0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252,
			0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
			0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60,
			0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
			0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
			0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
			0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04,
			0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,

			0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
			0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
			0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
			0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
			0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E,
			0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
			0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
			0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
			0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
			0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
			0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0,
			0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
			0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6,
			0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
			0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
			0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D};

            static public uint BeginChecksum()
            {
                return 0xffffffff;
            }

            static public uint UpdateChecksum(uint crc, byte[] bytes, int offset, int count)
            {
                for (int i = offset; i < count; i++)
                {
                    byte index = (byte)(((crc) & 0xff) ^ bytes[i]);
                    crc = (uint)((crc >> 8) ^ table[index]);
                }

                return crc;
            }

            static public uint EndChecksum(uint crc)
            {
                return ~crc;
            }
        }

        // -------------------------------------------------------------------------------------------------

        class CounterStream : Stream
        {
            int Count { get; set; }
            Stream InternalStream { get; set; }
            bool LeaveOpen { get; set; }

            public CounterStream(Stream stream, bool leaveOpen)
            {
                if (stream == null)
                {
                    throw new ArgumentNullException("stream");
                }

                InternalStream = stream;
                LeaveOpen = leaveOpen;
            }

            public override bool CanRead
            {
                get { return false; }
            }

            public override bool CanSeek
            {
                get { return false; }
            }

            public override bool CanWrite
            {
                get { return InternalStream.CanWrite; }
            }

            public override void Flush()
            {
                InternalStream.Flush();
            }

            public override long Length
            {
                get { throw new System.NotSupportedException(); }
            }

            public override long Position
            {
                get
                {
                    return Count;
                }
                set
                {
                    throw new System.NotSupportedException();
                }
            }

            public override int Read(byte[] buffer, int offset, int count)
            {
                throw new System.NotSupportedException();
            }

            public override long Seek(long offset, SeekOrigin origin)
            {
                throw new System.NotSupportedException();
            }

            public override void SetLength(long value)
            {
                throw new System.NotSupportedException();
            }

            public override void Write(byte[] buffer, int offset, int count)
            {
                InternalStream.Write(buffer, offset, count);
                Count += count;
            }

            protected override void Dispose(bool disposing)
            {
                try
                {
                    if (disposing)
                    {
                        if (InternalStream != null && LeaveOpen == false)
                        {
                            InternalStream.Close();
                        }
                    }
                }

                finally
                {
                    InternalStream = null;
                    base.Dispose(disposing);
                }
            }
        }
        
        // -------------------------------------------------------------------------------------------------

        static uint GetDosDateTime(DateTime date)
        {
            return (uint)(
                ((date.Year - 1980 & 0x7f) << 25) |
                ((date.Month & 0xF) << 21) |
                ((date.Day & 0x1F) << 16) |
                ((date.Hour & 0x1F) << 11) |
                ((date.Minute & 0x3F) << 5) |
                (date.Second >> 1));
        }

        static void Write(uint number, Stream stream)
        {
            byte[] buffer = BitConverter.GetBytes(number);
            stream.Write(buffer, 0, buffer.Length);
        }

        static void Write(ushort value, Stream stream)
        {
            byte[] buffer = BitConverter.GetBytes(value);
            stream.Write(buffer, 0, buffer.Length);
        }

        static void Write(byte[] buffer, Stream stream)
        {
            stream.Write(buffer, 0, buffer.Length);
        }

        static string NormalizeFileName(string fileName)
        {
            string name = fileName.Replace("\\", "/");
            name = name.Replace("../", "");
            int i = name.IndexOf(':');

            if (i != -1)
            {
                name = name.Substring(i + (Path.IsPathRooted(name) ? 2 : 1));
            }

            return name;
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions