/// <summary> /// zipファイル内のファイル名を指定しての読み込み。 /// </summary> /// <param name="filename">zipファイル名</param> /// <param name="innerFileName">zip内のファイル名</param> /// <param name="bRead">読み込むのか?読み込むときはbuffにその内容が返る</param> /// <param name="buff"></param> /// <returns> /// bRead=falseのとき、ヘッダ内に該当ファイルが存在すればtrue /// bRead=trueのとき、読み込みが成功すればtrue /// </returns> /// <summary> /// zipファイル内のファイル名を指定しての読み込み。 /// /// 大文字小文字の違いは無視する。 /// </summary> /// <param name="filename">zipファイル名</param> /// <param name="innerFileName">zip内のファイル名</param> /// <param name="bRead">読み込むのか?読み込むときはbuffにその内容が返る</param> /// <param name="buff"></param> /// <returns> /// bRead=falseのとき、ヘッダ内に該当ファイルが存在すればtrue /// bRead=trueのとき、読み込みが成功すればtrue /// </returns> public bool Read(string filename , string innerFileName , bool bRead , out byte[] buff) { // .NET Framework2.0には System.Io.CompressionにGZipStreamというクラスがある。 // これはZipStreamを扱うクラスなのでファイルを扱う部分は自前で用意してやる必要がある // cf. // http://msdn2.microsoft.com/en-us/library/zs4f0x23.aspx buff = null; if ( ! reader.IsExist(filename) ) goto Exit; Stream file = reader.Read(filename); if ( file == null ) goto Exit; // おそらくは存在しないか二重openか。 // 二重openできないので、使いおわったら必ずCloseしなくてはならない。 // そのためにtry~finallyで括ることにする。 try { StreamRandAccessor acc = new StreamRandAccessor(file); Dictionary<string,object> cacheList = null; // 読み込みcacheが有効らしい if ( readCache ) { filename = filename.ToLower(); if ( arcFileList.IsExistArchive(filename) ) { // このarcFileListのなかから探し出す // 格納されているファイル名をToLower等で正規化しておく必要あり object obj; try { innerFileName = innerFileName.ToLower(); obj = arcFileList.ArchiveList[filename][innerFileName]; } catch { return false; // 見つからない } // ここで取得したobjを元にファイルから読み込む InnerZipFileInfo info = obj as InnerZipFileInfo; uint localHeaderPos = info.localHeaderPos; uint compressed_size = info.compressed_size; uint uncompressed_size = info.uncompressed_size; return innerExtract(acc , localHeaderPos , bRead , compressed_size , uncompressed_size , out buff); } // このファイルにファイルリスト読み込むのが先決では… cacheList= new Dictionary<string,object>(); arcFileList.ArchiveList.Add(filename,cacheList); } // Find 'end record index' by searching backwards for signature long stoppos; // ulongって引き算とか比較が面倒くさいですなぁ..(´Д`) if ( file.Length < 66000 ) { stoppos = 0; } else { stoppos = acc.Length - 66000; } long endrecOffset = 0; for ( long i = acc.Length - 22 ; i >= stoppos ; --i ) { if ( acc.GetUint(i) == 0x06054b50 ) { // "PK\0x05\0x06" ushort endcommentlength = acc.GetUshort(i + 20); if ( i + 22 + endcommentlength != acc.Length ) continue; endrecOffset = i; } goto endrecFound; } // ダメジャン goto Exit; endrecFound: ; // ---- endrecOffsetが求まったナリよ! ushort filenum = acc.GetUshort(endrecOffset + 10); // zipに格納されているファイルの数(分割zipは非対応) long c_pos = acc.GetUint(endrecOffset + 16); // central directoryの位置 // printf("filenum %d",filenum); // ---- central directoryが求まったなりよ! while ( filenum-- > 0 ) { if ( acc.GetUint(c_pos) != 0x02014b50 ) { // シグネチャー確認! goto Exit; // return false; // おかしいで、このファイル } uint compressed_size = acc.GetUint(c_pos + 20); uint uncompressed_size = acc.GetUint(c_pos + 24); ushort filename_length = acc.GetUshort(c_pos + 28); ushort extra_field_length = acc.GetUshort(c_pos + 30); ushort file_comment_length = acc.GetUshort(c_pos + 32); //printf("filenamelength : %d",filename_length); // local_header_pos uint lh_pos = acc.GetUint(c_pos + 42); // ファイル名の取得 StringBuilderEncoding fname = new StringBuilderEncoding(filename_length); for ( int i = 0 ; i < filename_length ; ++i ) { fname.Add(acc.GetByte(i + c_pos + 46)); } // printf("%.*name\n",fname); // ファイル名が得られた。 // string fullfilename = dirname + fname; // yield return fullfilename; if ( cacheList!=null ) { // readCacheが有効なら、まずは格納していく。 cacheList.Add(fname.ToString().ToLower() , new InnerZipFileInfo(lh_pos , compressed_size , uncompressed_size)); } else { // ファイル名の一致比較は、大文字小文字の違いは無視する。 // // Windows環境ではファイルシステムは、ファイルの大文字小文字の違いは無視するが、 // いざリリースのときにzipフォルダにまとめたせいでいままで動いていたものが // 動かなくなると嫌なので。 if ( fname.Convert(codePage).Equals(innerFileName , StringComparison.CurrentCultureIgnoreCase) ) { // 一致したでー!これ読み込もうぜー! return innerExtract(acc , lh_pos , bRead , compressed_size , uncompressed_size , out buff); } } // さーて、来週のサザエさんは.. c_pos += 46 + filename_length + extra_field_length + file_comment_length; } } finally { if ( file != null ) file.Dispose(); } // readCacheが有効なのに、ここまで到達するというのは、 // archive内のfile listをcacheしていなかったので調べていたに違いない。 // よって、再帰的に呼び出すことによって解決できる。 if ( readCache ) return Read(filename , innerFileName , bRead , out buff); Exit: ; buff = null; return false; }
/// <summary> /// Zipファイル解凍のための内部メソッド /// </summary> /// <param name="acc"></param> /// <param name="lh_pos">lh_pos == local header position</param> /// <param name="bRead"></param> /// <param name="buff"></param> /// <returns></returns> private bool innerExtract(StreamRandAccessor acc , uint lh_pos,bool bRead, uint compressed_size, uint uncompressed_size, out byte[] buff) { buff = null; // 読み込み指定されてなければ // ファイルが存在することを意味するtrueを返す if ( !bRead ) return true; if ( acc.GetUint(lh_pos) != 0x04034b50 ) { // なんで?ヘッダのシグネチャー違うやん.. return false; } ushort lh_flag = acc.GetUshort(lh_pos + 6); ushort lh_compression_method = acc.GetUshort(lh_pos + 8); ushort lh_filename_length = acc.GetUshort(lh_pos + 26); ushort lh_extra_field = acc.GetUshort(lh_pos + 28); long startpos = lh_pos + 30 + lh_filename_length + lh_extra_field; // データの読み込み // データの読み込み bool encry = ( lh_flag & 1 ) != 0; byte[] crypt_header = null; if ( encry ) { crypt_header = new byte[12]; if ( acc.Read(crypt_header , startpos , 12) != 12 ) return false; compressed_size -= 12; startpos += 12; } byte[] read_buffer = new byte[compressed_size]; long readsize = acc.Read(read_buffer , startpos , compressed_size); // これ0ってことはあるのか..? まあ空のファイルかも知れないんで考慮する必要はないか。 if ( readsize != compressed_size ) return false; // 読み込みエラー //printf("lh_compression_method : %d",lh_compression_method); if ( encry ) { // 暗号化ファイル /* 暗号の解読 */ ZipPassUpdate zipupdate = new ZipPassUpdate(); zipupdate.initKey(); foreach ( char c in zippass ) zipupdate.updateKeys(( byte ) c); // Read the 12-byte encryption header into Buffer for ( int i = 0 ; i < 12 ; ++i ) { byte c = ( byte ) ( crypt_header[i] ^ zipupdate.decrypt_byte() ); zipupdate.updateKeys(c); crypt_header[i] = c; } for ( int i = 0 ; i < compressed_size ; ++i ) { byte c = ( byte ) ( read_buffer[i] ^ zipupdate.decrypt_byte() ); zipupdate.updateKeys(c); read_buffer[i] = c; } } switch ( lh_compression_method ) { case 0: // 無圧縮のようなので、これをそのまま返す buff = read_buffer; return true; case 8: byte[] decompressedBuffer; // zlibを用いて解凍 // -15 is a magic value used to decompress zip files. // It has the effect of not requiring the 2 byte header // and 4 byte trailer. // data = (cast(void[])read_buffer,uncompressed_size,-15); #region やねうコメント /* ↓以下、D版のyanesdkを作っていたときの掲示板でのやりとりから。 706 ナマエ:やねうらお◆Ze9R3gKs 2004/01/20(火) 07:57 [192.168.1.*] [MSIE6.0/WindowsXP] zipの暗号化書庫に対応させるの実装してみますた。 でも、うまく解凍されナーです。 くぅ..。 phobosのzlib.dを見ると // -15 is a magic value used to decompress zip files. // It has the effect of not requiring the 2 byte header // and 4 byte trailer. とか書いてあって、uncompressの第3パラメータに-15を渡してる みたいで..この-15ってのが何なんやという..。winbits??なんやこれ.. -------------------------------------------------------------------------------- 707 ナマエ:やねうらお◆Ze9R3gKs 2004/01/20(火) 08:06 [192.168.1.*] [Netscape7.02/WindowsXP] >>706 このパラメータはそのままzlibのinflateInit2に渡してる。このinflateInit2の仕様はここに書いてある。 http://www.sra.co.jp/people/m-kasahr/zlib/zlib-ja.h 引数 windowBits は、ウィンドウの大きさ (履歴バッファの大きさ) を 2 を底とする対数で指定する。このバージョンの zlib ライブラリでは、8 ~ 15 にすべきである。 だそうな。対数で指定するってどういうこっちゃ。2^nのnを指定するってことかいな?-15っちゅーと、ほぼ0ってことかな? -------------------------------------------------------------------------------- 708 ナマエ:やねうらお◆Ze9R3gKs 2004/01/20(火) 08:19 [192.168.1.*] [Netscape7.02/WindowsXP] >>707 ああ。意味わかった。暗号化zipの場合は12バイトの暗号化ヘッダがくっついとるから12足したアドレスをzlib.compressに渡せばいいんだな。12バイト足したアドレスって言ってもvoid[]を取るからコピーして渡すか何かしなきゃいけないんだけど、さすがにそんなでかいデータをコピーするわけにはいかないから、事前にうまいことやって読み込んでおく必要がある、と。 -------------------------------------------------------------------------------- 709 ナマエ:k.inaba 2004/01/20(火) 08:34 [*.home.ne.jp] [MSIE6.0/WindowsXP] >>707 暗号化とは直接関係ないですがちなみに、deflateInit2 / inflateInit2 の windowBits は int バッファサイズ = 2^abs(windowBits); bool 圧縮後データブロックの先頭などにCheckSumが入るフラグ = (windowBits >= 0); てな感じに使われるので、-15だと「32768byteのバッファ + ヘッダ無し」になりまする。 -------------------------------------------------------------------------------- 710 ナマエ:やねうらお◆Ze9R3gKs 2004/01/20(火) 08:49 [192.168.1.*] [MSIE6.0/WindowsXP] >>709 そ、、そうなんですか。納得! そんなわけで皆様のおかげでパスワード付きのzipファイルを シームレスに読み込めるようになりました。 この場をお借りしてお礼申し上げます。 ありがとうございますm(_ _)m */ #endregion /* decompressedBuffer = new byte[uncompressed_size]; MemoryStream ms = new MemoryStream(); ms.Write(read_buffer, 0, (int)compressed_size); ms.Position = 12; GZipStream zipStream = new GZipStream(ms, CompressionMode.Decompress); int readcount = zipStream.Read(decompressedBuffer, 0, (int)uncompressed_size); if (readcount != uncompressed_size) { buff = null; // あかん。なんやこれ。解凍失敗や return false; } */ // どうも、.NET2.0の GZipStreamは辞書サイズを指定できないので // zipファイルと互換にならないようだ.. if (useZlib) { // zlibを使う実装 // cf. //http://www.codeproject.com/managedcpp/mcppzlibwrapper.asp bool bError = false; decompressedBuffer = new byte[uncompressed_size]; ZLibWrapper.ZStream stream = new ZLibWrapper.ZStream(); try { int resultInflateInit = ZLibWrapper.inflateInit2(ref stream, -15); if (resultInflateInit == ZLibWrapper.Z_OK) { unsafe { fixed (byte* outBuffer = decompressedBuffer) fixed (byte* inBuffer = read_buffer) { stream.next_in = (IntPtr)inBuffer; stream.avail_in = compressed_size; stream.next_out = (IntPtr)outBuffer; stream.avail_out = uncompressed_size; int resultInflate = ZLibWrapper.inflate(ref stream, ZLibWrapper.Z_NO_FLUSH); if (resultInflate < 0) { bError = true; } } } } else { bError = true; } } catch { bError = true; } finally { // 後始末する int resultInflateEnd = ZLibWrapper.inflateEnd(ref stream); if (resultInflateEnd != ZLibWrapper.Z_OK) { bError = true; } } if ( bError ) { // 展開エラー buff = null; return false; } // 解凍いけたいけた buff = decompressedBuffer; return true; } else { // zlibを用いずに自前で展開する。 decompressedBuffer = new byte[uncompressed_size]; MemoryStream ms = new MemoryStream(); ms.Write(read_buffer, 0, (int)compressed_size); ms.Position = 0; DeflateStream zipStream = new DeflateStream(ms, CompressionMode.Decompress); int readcount = zipStream.Read(decompressedBuffer, 0, (int)uncompressed_size); if (readcount != uncompressed_size) { buff = null; // あかん。なんやこれ。解凍失敗や return false; } // 解凍いけたいけた buff = decompressedBuffer; return true; } default: // 対応してないzip圧縮 buff = null; return false; } }
public override IEnumerator GetEnumerator() { // ZipStream zip = new GZipStream(filename); // foreach (ZipEntry e in zip) { // yield return e.Name; // } Stream file = null; if ( !reader.IsExist(filename) ) goto Exit; file = reader.Read(filename); if ( file == null ) goto Exit; // おそらくは存在しないか二重openか。 StreamRandAccessor acc = new StreamRandAccessor(file); // Find 'end record index' by searching backwards for signature long stoppos; // ulongって引き算とか比較が面倒くさいですなぁ..(´Д`) if ( file.Length < 66000 ) { stoppos = 0; } else { stoppos = acc.Length - 66000; } long endrecOffset = 0; for ( long i = acc.Length - 22 ; i >= stoppos ; --i ) { if ( acc.GetUint(i) == 0x06054b50 ) { // "PK\0x05\0x06" ushort endcommentlength = acc.GetUshort(i + 20); if ( i + 22 + endcommentlength != acc.Length ) continue; endrecOffset = i; } goto endrecFound; } // ダメジャン goto Exit; endrecFound: ; // ---- endrecOffsetが求まったナリよ! ushort filenum = acc.GetUshort(endrecOffset + 10); // zipに格納されているファイルの数(分割zipは非対応) long c_pos = acc.GetUint(endrecOffset + 16); // central directoryの位置 // printf("filenum %d",filenum); // ---- central directoryが求まったなりよ! while ( filenum-- > 0 ) { if ( acc.GetUint(c_pos) != 0x02014b50 ) { // シグネチャー確認! goto Exit; // return false; // おかしいで、このファイル } uint compressed_size = acc.GetUint(c_pos + 20); uint uncompressed_size = acc.GetUint(c_pos + 24); ushort filename_length = acc.GetUshort(c_pos + 28); ushort extra_field_length = acc.GetUshort(c_pos + 30); ushort file_comment_length = acc.GetUshort(c_pos + 32); //printf("filenamelength : %d",filename_length); // local_header_pos uint lh_pos = acc.GetUint(c_pos + 42); // ファイル名の取得 StringBuilderEncoding fname = new StringBuilderEncoding(filename_length); for ( int i = 0 ; i < filename_length ; ++i ) { fname.Add(acc.GetByte(i + c_pos + 46)); } // printf("%.*name\n",fname); // ファイル名が得られた。 string fullfilename = dirname + fname.Convert(codePage); yield return fullfilename; // さーて、来週のサザエさんは.. c_pos += 46 + filename_length + extra_field_length + file_comment_length; } Exit: ; if ( file != null ) file.Dispose(); }