.netでZipファイルを操作する

DotNet Zip Library
http://www.codeplex.com/DotNetZip
より。


Project Description
The System.IO.Compression namespace in the Microsoft .NET Framework {v2.0 v3.0 v3.5} includes base class libraries supporting compression within streams - both the Deflate (ietf rfc-1951) and Gzip (ietf rfc-1952) formats are supported. But the System.IO.Compression namespace provides streaming compression only - useful for communicating between cooperating parties but not directly useful for creating compressed archives, like .zip files, the format for which is defined by PKWare Inc. The System.IO.Compression namespace does not directly support formatting zip archive headers and so on.

To address this, this class library augments the System.IO.Compression.DeflateStream class, to provide handling for Zip files. Using this library, you can build .NET applications that read and write zip-format files.


.NET Framework {v2.0 v3.0 v3.5}にはストリーム圧縮(DeflateとGzip)をサポートするSystem.IO.Compressionが存在します。
System.IO.Compressionは協調関係にある組織との通信には便利ですが、圧縮アーカイブ(.zipファイルなど)を作成するのには適していません。
ZipファイルのフォーマットはPKWare Inc.によって定義されていますが、System.IO.Compressionはzipアーカイブヘッダなどをサポートしていません。

この問題を解消するため、このクラスではSystem.IO.Compression.DeflateStreamを拡張し、zipファイルを取り扱えるようにしています。
このクラスを使うことによって、.NETアプリケーションでzipファイルを読み書きすることができるようになっています。


便利そうなんだが、ファイル名・ディレクトリ名に日本語が含まれていると文字化けするので、
(http://forums.microsoft.com/MSDN-JA/ShowPost.aspx?PostID=2485689&SiteID=7)
(http://blogs.wankuma.com/torikobito/archive/2008/02/06/121561.aspx)
システムのデフォルトエンコーディングエンコード・デコードするようにしてみた。
修正点は下記の箇所。

Ionic.Utils.Zip.Shared

        protected internal static string StringFromBuffer(byte[] buf, int start, int maxlength)
        {

            //int i;
            //char[] c = new char[maxlength];
            //for (i = 0; (i < maxlength) && (i < buf.Length) && (buf[i] != 0); i++)
            //{
            //    c[i] = (char)buf[i]; // System.BitConverter.ToChar(buf, start+i*2);
            //}
            //string s = new System.String(c, 0, i);
            //return s;

            System.Text.Encoding encoding = System.Text.Encoding.Default;
            string s = encoding.GetString(buf);
            return s;
        }


Ionic.Utils.Zip.ZipEntry

        private void WriteHeader(System.IO.Stream s, byte[] bytes)
        {
            // write the header info

            int i = 0;
            // signature
            bytes[i++] = (byte)(ZipConstants.ZipEntrySignature & 0x000000FF);
            bytes[i++] = (byte)((ZipConstants.ZipEntrySignature & 0x0000FF00) >> 8);
            bytes[i++] = (byte)((ZipConstants.ZipEntrySignature & 0x00FF0000) >> 16);
            bytes[i++] = (byte)((ZipConstants.ZipEntrySignature & 0xFF000000) >> 24);

            // version needed
            Int16 FixedVersionNeeded = 0x14; // from examining existing zip files
            bytes[i++] = (byte)(FixedVersionNeeded & 0x00FF);
            bytes[i++] = (byte)((FixedVersionNeeded & 0xFF00) >> 8);

            // bitfield
            Int16 BitField = 0x00; // from examining existing zip files
            bytes[i++] = (byte)(BitField & 0x00FF);
            bytes[i++] = (byte)((BitField & 0xFF00) >> 8);

            Int16 CompressionMethod = 0x00; // 0x08 = Deflate, 0x00 == No Compression

            // compression for directories = 0x00 (No Compression)

            if (!IsDirectory)
            {
                CompressionMethod = 0x08;
                // CRC32 (Int32)
                if (_FileData != null)
                {
                    // If we have FileData, that means we've read this entry from an
                    // existing zip archive. We must just copy the existing file data, 
                    // CRC, compressed size, and uncompressed size 
                    // over to the new (updated) archive.  
                }
                else
                {
                    // special case zero-length files
                    System.IO.FileInfo fi = new System.IO.FileInfo(LocalFileName);
                    if (fi.Length == 0)
                    {
                        CompressionMethod = 0x00;
                        _UncompressedSize = 0;
                        _CompressedSize = 0;
                        _Crc32 = 0;

                    }
                    else
                    {
                        // Read in the data from the file in the filesystem, compress it, and 
                        // calculate a CRC on it as we read. 

                        CRC32 crc32 = new CRC32();
                        using (System.IO.Stream input = System.IO.File.OpenRead(LocalFileName))
                        {
                            UInt32 crc = crc32.GetCrc32AndCopy(input, CompressedStream);
                            _Crc32 = (Int32)crc;
                        }
                        CompressedStream.Close();  // to get the footer bytes written to the underlying stream
                        _CompressedStream = null;

                        _UncompressedSize = crc32.TotalBytesRead;
                        _CompressedSize = (Int32)_UnderlyingMemoryStream.Length;

                        // It is possible that applying this stream compression on a previously compressed
                        // file will actually increase the size of the data.  In that case, we back-off
                        // and just store the uncompressed (really, already compressed) data.
                        // We need to recompute the CRC, and point to the right data.
                        if (_CompressedSize > _UncompressedSize)
                        {
                            using (System.IO.Stream input = System.IO.File.OpenRead(LocalFileName))
                            {
                                _UnderlyingMemoryStream = new System.IO.MemoryStream();
                                UInt32 crc = crc32.GetCrc32AndCopy(input, _UnderlyingMemoryStream);
                                _Crc32 = (Int32)crc;
                            }
                            _UncompressedSize = crc32.TotalBytesRead;
                            _CompressedSize = (Int32)_UnderlyingMemoryStream.Length;
                            if (_CompressedSize != _UncompressedSize) throw new Exception("No compression but unequal stream lengths!");
                            CompressionMethod = 0x00;
                        }
                    }
                }
            }
            // compression method         
            bytes[i++] = (byte)(CompressionMethod & 0x00FF);
            bytes[i++] = (byte)((CompressionMethod & 0xFF00) >> 8);

            // LastMod
            bytes[i++] = (byte)(_LastModDateTime & 0x000000FF);
            bytes[i++] = (byte)((_LastModDateTime & 0x0000FF00) >> 8);
            bytes[i++] = (byte)((_LastModDateTime & 0x00FF0000) >> 16);
            bytes[i++] = (byte)((_LastModDateTime & 0xFF000000) >> 24);

            // calculated above
            bytes[i++] = (byte)(_Crc32 & 0x000000FF);
            bytes[i++] = (byte)((_Crc32 & 0x0000FF00) >> 8);
            bytes[i++] = (byte)((_Crc32 & 0x00FF0000) >> 16);
            bytes[i++] = (byte)((_Crc32 & 0xFF000000) >> 24);

            // CompressedSize (Int32)
            bytes[i++] = (byte)(_CompressedSize & 0x000000FF);
            bytes[i++] = (byte)((_CompressedSize & 0x0000FF00) >> 8);
            bytes[i++] = (byte)((_CompressedSize & 0x00FF0000) >> 16);
            bytes[i++] = (byte)((_CompressedSize & 0xFF000000) >> 24);

            // UncompressedSize (Int32)
            if (_Debug) System.Console.WriteLine("Uncompressed Size: {0}", _UncompressedSize);
            bytes[i++] = (byte)(_UncompressedSize & 0x000000FF);
            bytes[i++] = (byte)((_UncompressedSize & 0x0000FF00) >> 8);
            bytes[i++] = (byte)((_UncompressedSize & 0x00FF0000) >> 16);
            bytes[i++] = (byte)((_UncompressedSize & 0xFF000000) >> 24);

            // filename length (Int16)
            // the filename written to the archive
            char[] c = ((TrimVolumeFromFullyQualifiedPaths) && (FileName[1] == ':') && (FileName[2] == '\\')) ?
          FileName.Substring(3).Replace("\\", "/").ToCharArray() :  // trim off volume letter, colon, and slash
          FileName.Replace("\\", "/").ToCharArray();
            int j = 0;

            if (_Debug)
            {
                System.Console.WriteLine("local header: writing filename, {0} chars", c.Length);
                System.Console.WriteLine("starting offset={0}", i);
            }

//修正開始
            byte[] encodedfilename = System.Text.Encoding.Default.GetBytes(c);
            j = encodedfilename.Length;

            Int16 filenameLength = (Int16)j;
            // see note below about TrimVolumeFromFullyQualifiedPaths.
            if ((TrimVolumeFromFullyQualifiedPaths) && (FileName[1] == ':') && (FileName[2] == '\\')) filenameLength -= 3;
            // apply upper bound to the length
            if (filenameLength + i > bytes.Length) filenameLength = (Int16)(bytes.Length - (Int16)i);
            bytes[i++] = (byte)(filenameLength & 0x00FF);
            bytes[i++] = (byte)((filenameLength & 0xFF00) >> 8);

            // extra field length (short)
            Int16 ExtraFieldLength = 0x00;
            bytes[i++] = (byte)(ExtraFieldLength & 0x00FF);
            bytes[i++] = (byte)((ExtraFieldLength & 0xFF00) >> 8);

            
            Array.Copy(encodedfilename, 0, bytes, i, j);
//修正終了
            if (_Debug) System.Console.WriteLine();

            i += j;

            // extra field (we always write nothing in this implementation)
            // ;;

            // remember the file offset of this header
            _RelativeOffsetOfHeader = (int)s.Length;

            if (_Debug)
            {
                System.Console.WriteLine("\nAll header data:");
                for (j = 0; j < i; j++)
                    System.Console.Write(" {0:X2}", bytes[j]);
                System.Console.WriteLine();
            }
            // finally, write the header to the stream
            s.Write(bytes, 0, i);

            // preserve this header data for use with the central directory structure.
            _header = new byte[i];
            if (_Debug) System.Console.WriteLine("preserving header of {0} bytes", _header.Length);
            for (j = 0; j < i; j++)
                _header[j] = bytes[j];
        }