none
Boostをつかったzipファイルの読み込み

    質問

  • 題の通りです。boostを使ってzipファイルを読み込みたいのですが、例外が発生してしまい、うまくいきません。今は、zipから圧縮されたデータのみを抜き出してboostに渡しているのですが、違うのでしょうか?また、wikipediaにはLocalFileHeaderの変数は、リトルエンディアンで格納されるとありますが、どうもその様子がありません。(7zipとエクスプローラーで圧縮させました)これは普通のことなのでしょうか。

    例外:

    0x76385608 で例外がスローされました (BoostTest.exe 内): Microsoft C++ の例外: boost::exception_detail::clone_impl<boost::exception_detail::error_info_injector<boost::iostreams::zlib_error> > (メモリの場所 0x002CED34)。
    0x76385608 で例外がスローされました (BoostTest.exe 内): Microsoft C++ の例外: boost::exception_detail::clone_impl<boost::exception_detail::error_info_injector<boost::iostreams::zlib_error> > (メモリの場所 0x002CED70)。
    0x76385608 で例外がスローされました (BoostTest.exe 内): Microsoft C++ の例外: boost::exception_detail::clone_impl<boost::exception_detail::error_info_injector<boost::iostreams::zlib_error> > (メモリの場所 0x002CE26C)。
    0x76385608 で例外がスローされました (BoostTest.exe 内): Microsoft C++ の例外: boost::exception_detail::clone_impl<boost::exception_detail::error_info_injector<boost::iostreams::zlib_error> > (メモリの場所 0x002CDF64)。
    0x76385608 で例外がスローされました (BoostTest.exe 内): Microsoft C++ の例外: boost::exception_detail::clone_impl<boost::exception_detail::error_info_injector<boost::iostreams::zlib_error> > (メモリの場所 0x002CDDA8)。
    0x76385608 で例外がスローされました (BoostTest.exe 内): Microsoft C++ の例外: boost::exception_detail::clone_impl<boost::exception_detail::error_info_injector<boost::iostreams::zlib_error> > (メモリの場所 0x002CDFA0)。
    0x76385608 で例外がスローされました (BoostTest.exe 内): Microsoft C++ の例外: [rethrow] (メモリの場所 0x00000000)。
    0x76385608 で例外がスローされました (BoostTest.exe 内): Microsoft C++ の例外: [rethrow] (メモリの場所 0x00000000)。

    test.zipはこのソースコードをtest.txtに変換して圧縮したものです。

    ソースコード:

    #include "stdafx.h"
    
    namespace zip
    {
    
    #pragma pack(1)
    typedef struct _tagEOCD
    {
    	int16_t           _diskCount;
    	int16_t           _diskNumber;
    	int16_t           _CDcount;
    	int16_t           _CDcountAll;
    	int32_t           _CDsize;
    	int32_t           _CDoffset;
    	int16_t           _commentLength;
    	char*             _comment;      //?
    }ZIP_EOCD;
    
    typedef struct _tagLocalFileHeader
    {
    	int8_t  _signature[4];
    	int16_t _minVer;
    	int16_t _bitFlag;
    	int16_t _method;
    	int16_t _lastModTime;
    	int16_t _lastModDate;
    	int32_t _CRC32;
    	int32_t _zippedSize;
    	int32_t _originalSize;
    	int16_t _fNameLen;
    	int16_t _exFieldLen;
    
    }LocalFileHeader;
    
    typedef struct _tagDataDescriptor
    {
    	int32_t _field[4]; //16byte
    }DataDescriptor;
    
    typedef struct _tagCentralDirectory
    {
    	uint8_t  _signnature[4];
    	uint16_t _formatVer;
    	uint16_t _minVer;
    	uint16_t _bitFlag;
    	uint16_t _method;
    	uint16_t _modTime;
    	uint16_t _modDate;
    	uint32_t _crc_32;
    	uint32_t _compressed_size;
    	uint32_t _uncompressed_size;
    	uint16_t _fNameLen;
    	uint16_t _exFieldLen;
    	uint16_t _fCommentLen;
    	uint16_t _diskNumberStart;
    	uint16_t _inFileAttr;
    	uint32_t _exFileAttr;
    	uint32_t _lhfOffset;
    
    }CentralDirectory;
    #pragma pack()
    
    bool operator==(const CentralDirectory& lhs, const CentralDirectory& rhs)
    {
    	return (memcmp(&lhs, &rhs, sizeof(CentralDirectory)) == 0);
    }
    
    bool operator!=(const CentralDirectory& lhs, const CentralDirectory& rhs)
    {
    	return !(lhs == rhs);
    }
    
    class unzip
    {
    private:
    	typedef struct _tagZipFileEntry
    	{
    		std::string          _name;
    		CentralDirectory     _info;
    	}ZipFileEntry;
    
    public:
    
    	explicit unzip(const char* FileName) :
    		_zipFileName(FileName)
    	{
    		std::ifstream _InStream(FileName);
    
    		if (!_InStream) {
    			throw std::exception("cannot open zip file");
    		}
    
    		if (!_initializeZipEntry(_InStream)) {
    			throw std::exception("cannot initialize zip file entry");
    		}
    	}
    
    	bool decompress(const char *InternalFileName, std::vector<char>& Buffer) const;
    
    private:
    	bool _initializeZipEntry(std::ifstream& InStream);
    	CentralDirectory _getInfo(const char* FileName) const;
    
    	std::vector<ZipFileEntry> _zipEntry;
    	std::string               _zipFileName;
    };
    
    
    inline int16_t swapVal(const int16_t Value)
    {
    	return (Value << 8) | (Value >> 8);
    }
    
    inline int32_t swapVal(const int32_t Value)
    {
    	return (Value << 24) | ((Value & 0x0000ff00) << 8) | ((Value & 0x0000ff00) >> 8) |
    		(Value >> 24);
    }
    
    bool getEOCD(std::ifstream& InStream, ZIP_EOCD& Buffer)
    {
    	char _CurrentVal;
    	static constexpr char _Magic[4] = { 0x50,0x4b,0x05,0x06 };
    
    	//ファイル終端から探す
    	//シグネチャを見つけるので、最小サイズ(16)分戻しておく
    	InStream.seekg(-18, std::ios_base::end);
    	while (!InStream.bad()) {
    		InStream.read(&_CurrentVal, 1);
    
    		if (_CurrentVal == _Magic[3]) {
    			char _Temp[3];
    			//3Byte分読み込む
    			InStream.seekg(-4, std::ios_base::cur);
    			InStream.read(_Temp, 3);
    
    			//すでに1Byteは正しいことは確定している
    			if (!memcmp(_Temp, _Magic, sizeof(_Magic) - 1)) {
    				break; //正しかった
    			}
    			else {
    				//2Byte進めてやり直し
    				InStream.seekg(2, std::ios_base::cur);
    				continue;
    			}
    		}
    
    		//1byte分戻す(1Byteは読み込むときに進むため+1)
    		InStream.seekg(-2, std::ios_base::cur);
    	}
    
    	//0x06分進める
    	InStream.seekg(1, std::ios_base::cur);
    	//読み込んで終了
    	InStream.read(reinterpret_cast<char*>(&Buffer), sizeof(ZIP_EOCD));
    	//コメントは無視する
    	if (Buffer._commentLength > 0) {
    		InStream.seekg(Buffer._commentLength, std::ios_base::cur);
    	}
    
    	return InStream.eof();
    }
    
    bool unzip::decompress(const char* InternalFileName, std::vector<char>& Buffer) const
    {
    	//読み込み用stream
    	std::ifstream _InStream(_zipFileName, std::ios_base::binary);
    	//解凍
    	boost::iostreams::filtering_ostream _Stream;
    	//バッファ
    	LocalFileHeader _LFH;
    	std::vector<char> _Compressed;
    	//読み飛ばすサイズ
    	std::streamoff _SkipSize;
    	//CD
    	CentralDirectory _Info = _getInfo(InternalFileName);
    
    	if (_Info == CentralDirectory()) {
    		return false;
    	}
    
    	_InStream.seekg(_Info._lhfOffset, std::ios_base::beg);
    	_InStream.read(reinterpret_cast<char*>(&_LFH), sizeof(LocalFileHeader));
    	_SkipSize = _LFH._fNameLen + _LFH._exFieldLen;
    
    	//圧縮されたデータを読み込む
    	_Compressed.resize(_LFH._zippedSize);
    	_InStream.seekg(_SkipSize, std::ios_base::cur);
    	_InStream.read(&_Compressed[0], _LFH._zippedSize);
    
    	if (_LFH._bitFlag&(1 << 3)) {
    		DataDescriptor _Temp;
    		_InStream.read(reinterpret_cast<char*>(&_Temp), sizeof(DataDescriptor));
    		int _Move = (_Temp._field[0] == 0x504b0708);
    
    		//結果をセット
    		_LFH._CRC32 = _Temp._field[_Move];
    		_LFH._zippedSize = _Temp._field[++_Move];
    		_LFH._originalSize = _Temp._field[++_Move];
    	}
    
    	//解凍
    	_Stream.push(boost::iostreams::zlib_decompressor());
    	_Stream.push(boost::iostreams::back_inserter(Buffer));
    
    	//バッファ確保
    	try {
    		boost::iostreams::write(_Stream, &_Compressed[0], _LFH._zippedSize);
    	}
    	catch (std::exception& e) {
    		printf(e.what()); //ここ
    		return false;
    	}
    	return true;
    }
    
    bool unzip::_initializeZipEntry(std::ifstream& InStream)
    {
    	ZIP_EOCD _EocdBuffer;
    
    	if (!getEOCD(InStream, _EocdBuffer)) {
    		return false;
    	}
    
    	//バッファを確保
    	_zipEntry.resize(_EocdBuffer._CDcountAll);
    
    	//移動
    	InStream.clear();
    	InStream.seekg(_EocdBuffer._CDoffset, std::ios_base::beg);
    
    	for (auto&& e:_zipEntry) {
    		//読み込み
    		InStream.read(reinterpret_cast<char*>(&e._info), sizeof(CentralDirectory));
    		//バッファ確保
    		e._name.resize(e._info._fNameLen);
    		//ファイル名読み込み
    		InStream.read(&e._name[0], e._info._fNameLen);
    		//それ以外は読み飛ばす
    		InStream.seekg(e._info._exFieldLen + e._info._fCommentLen,std::ios::cur);
    	}
    
    	return true;
    }
    
    CentralDirectory unzip::_getInfo(const char * FileName) const
    {
    	for (auto&& e : _zipEntry) {
    		if (e._name == FileName) {
    			return e._info;
    		}
    	}
    
    	//見つからなかった
    	return CentralDirectory();
    }
    
    }
    
    int main()
    {
    	auto path = "C:\\boost\\test.zip";
    
    
    	zip::unzip _Test(path);
    	std::vector<char> _Buffer;
    
    	_Test.decompress("test.txt", _Buffer);
    
    	return 0;
    
    }


    • 編集済み 山本啓太 2018年3月29日 3:11 コードブロック未使用
    2018年3月28日 10:20

回答

  • 質問文に開発、実行環境が記載されていないので確実なことは言えませんが、

    通常のWindowsはリトルエンディアンですので、swapValを経由させる必要はありません。なお、uint32_t等の代わりにBoost.Endianboost::endian::little_uint32_buf_t等を使うことでプラットフォームに依存しないコーディングも可能です。

    もしかするとですが、Boost.Iostreamsでは解凍できないのかもしれません。zlibライブラリでは

    • RFC 1950 zlib
    • RFC 1951 deflate
    • RFC 1952 gzip

    の3種類のフォーマットに対応しています。zipファイル形式の「8 - The file is Deflated」ではRFC 1951 deflateが使われています。ところがBoost.Iostreamsのfilterにはboost::iostreams::zlibboost::iostreams::gzipが用意されていますが肝心のdeflateがありません。


    • 編集済み 佐祐理 2018年3月29日 8:31
    • 回答としてマーク 山本啓太 2018年3月29日 12:15
    2018年3月29日 8:30

すべての返信

  • boostを使ってzipファイルを読み込みたいのですが、例外が発生してしまい、うまくいきません。今は、zipから圧縮されたデータのみを抜き出してboostに渡しているのですが、違うのでしょうか?
    _methodフィールドが「8 - The file is Deflated」を指している場合はdeflate圧縮されていると思います。
    wikipediaにはLocalFileHeaderの変数は、リトルエンディアンで格納されるとありますが、どうもその様子がありません。(7zipとエクスプローラーで圧縮させました)これは普通のことなのでしょうか。
    もちろんWikipediaの記述通りリトルエンディアンです。各シグネチャが 0x50, 0x4B, ?, ? の順に並んでいれば確実にリトルエンディアンです。逆に何をもってリトルエンディアンでないと判断したのでしょうか? 圧縮されたデータが期待通りに読み出せないことであれば筋違いです。圧縮データはバイト配列でありエンディアンの影響を受けません。
    2018年3月29日 5:22
  • ご返信ありがとうございます。LEであると仮定した場合、圧縮/非圧縮サイズを得るためにはswapValを使わなければなりませんが、これを使うとマイナスの値を示してしまう(一応以下のように符号なしにしましたが、同じよなものです...)こと、_zippedSizeのそのままの値(2499)が7zipの「圧縮後のサイズ」と合致したこと、この二点をもってLEで格納されていないと判断した次第です。

    //符号ありから符号なしに修正
    typedef struct _tagEOCD
    {
    	uint16_t           _diskCount;
    	uint16_t           _diskNumber;
    	uint16_t           _CDcount;
    	uint16_t           _CDcountAll;
    	uint32_t           _CDsize;
    	uint32_t           _CDoffset;
    	uint16_t           _commentLength;
    	char*             _comment;      //?
    }ZIP_EOCD;
    
    typedef struct _tagLocalFileHeader
    {
    	uint8_t  _signature[4];
    	uint16_t _minVer;
    	uint16_t _bitFlag;
    	uint16_t _method;
    	uint16_t _lastModTime;
    	uint16_t _lastModDate;
    	uint32_t _CRC32;
    	uint32_t _zippedSize;
    	uint32_t _originalSize;
    	uint16_t _fNameLen;
    	uint16_t _exFieldLen;
    
    }LocalFileHeader;
    
    //中略
    
    inline uint16_t swapVal(const uint16_t Value)
    {
    	return (Value << 8) | (Value >> 8);
    }
    
    inline uint32_t swapVal(const uint32_t Value)
    {
    	return (Value << 24) | ((Value & 0x0000ff00) << 8) | ((Value & 0x0000ff00) >> 8) |
    		(Value >> 24);
    }

    また、bool unzip::decompress(const char* InternalFileName, std::vector<char>& Buffer) const 内に次のようなマクロを組み出力させました。マクロなど:

    #define LP(_Param) \ { \ std::stringstream _Buffer;\ _Buffer << #_Param << "=" << _Param << "\n"; \ OutputDebugStringA(_Buffer.str().c_str()); \ }

    LP(_LFH._zippedSize);
    LP(swapVal(_LFH._zippedSize));
    LP(_LFH._originalSize);
    LP(swapVal(_LFH._originalSize));
    LP((_LFH._method == 8));

    出力結果:

    _LFH._zippedSize=2499
    swapVal(_LFH._zippedSize)=3272146953
    _LFH._originalSize=11990
    swapVal(_LFH._originalSize)=3593338926
    (_LFH._method == 8)=1

    となっていますので、Deflate圧縮されていると思います。

    2018年3月29日 7:33
  • 質問文に開発、実行環境が記載されていないので確実なことは言えませんが、

    通常のWindowsはリトルエンディアンですので、swapValを経由させる必要はありません。なお、uint32_t等の代わりにBoost.Endianboost::endian::little_uint32_buf_t等を使うことでプラットフォームに依存しないコーディングも可能です。

    もしかするとですが、Boost.Iostreamsでは解凍できないのかもしれません。zlibライブラリでは

    • RFC 1950 zlib
    • RFC 1951 deflate
    • RFC 1952 gzip

    の3種類のフォーマットに対応しています。zipファイル形式の「8 - The file is Deflated」ではRFC 1951 deflateが使われています。ところがBoost.Iostreamsのfilterにはboost::iostreams::zlibboost::iostreams::gzipが用意されていますが肝心のdeflateがありません。


    • 編集済み 佐祐理 2018年3月29日 8:31
    • 回答としてマーク 山本啓太 2018年3月29日 12:15
    2018年3月29日 8:30
  • たびたびのご返信に感謝いたします。結果から言うと、boost::Iostreamsで解凍できました。以下コード。

    bool unzip::decompress(const char* InternalFileName, std::vector<char>& Buffer) const
    {
    //中略
    //圧縮されたデータを読み込む
    
    	_Compressed.resize(_LFH._zippedSize + 2); // 0x78,0x9c
    	_Compressed.insert(_Compressed.begin(), { 0x78,(char)0x9C });
    //後略

    振り回してしまい、申し訳ないです。また、boost::endian についても、これから学習したいと思います。(以下言い訳)


    --経緯--

    Boost::Iostreamsでは解凍できないかもしれません。とのことでしたので,zlibをダウンロードし、

    bool unzip::decompress(const char* InternalFileName, std::vector<char>& Buffer) const の一部を次のように変更いたしました。

    bool unzip::decompress(const char* InternalFileName, std::vector<char>& Buffer) const
    { 
    //中略
    
    	z_stream _Inf = { 0 };
    
    	if (inflateInit(&_Inf) != Z_OK) {
    		return false;
    	}
    
    	Buffer.resize(_LFH._originalSize);
    
    	//初期化と伸長
    	_Inf.avail_in = _LFH._zippedSize;
    	_Inf.next_in = reinterpret_cast<Bytef*>(_Compressed.data());
    	_Inf.avail_out = _LFH._originalSize;
    	_Inf.next_out = reinterpret_cast<Bytef*>(std::addressof(Buffer[0]));
    	auto _Ret = inflate(&_Inf, Z_NO_FLUSH);
    
    	//終了処理
    	inflateEnd(&_Inf);
    
    	return _Ret == Z_OK;
    } //終わり

    すると、_Inf.msg がincorrect header checkとなり、zipを解凍できませんでした。このことをいろいろ調べた結果、次のようなサイトを見つけました。

    http://www.vbforums.com/showthread.php?804887-RESOLVED-What-zLib-I-should-use

    このサイトによると,zlibで解凍するには次のような値を差し込まなければならないそうです。(圧縮レベルによって異なるそうですが...)

    Compress Level: -1   (Default) 
    0x78          0x9C
    
    Compress Level: 0 
    0x78          0x1
    
    Compress Level: 1 
    0x78          0x1
    
    Compress Level: 2 
    0x78          0x5E
    
    Compress Level: 3 
    0x78          0x5E
    
    Compress Level: 4 
    0x78          0x5E
    
    Compress Level: 5 
    0x78          0x5E
    
    Compress Level: 6 
    0x78          0x9C
    
    Compress Level: 7 
    0x78          0xDA
    
    Compress Level: 8 
    0x78          0xDA
    
    Compress Level: 9 (Max compressLevel)
    0x78          0xDA

    そして0x78,0x9cを差し込んだらうまくいった、という次第です。


    • 編集済み 山本啓太 2018年3月29日 11:37 リンク
    2018年3月29日 11:36
  • 解決して何よりです。

    既にお気づきかもしれませんが、RFC 1950 zlibとRFC 1952 gzipはRFC 1951 deflateに各々のヘッダー・フッターを付与しただけのもので、本質的には同じです。zlibヘッダーは列挙されたように特定の2バイトですのでそれを差し込むのが簡単です。
    正確にはフッターとしてAdler-32チェックサムの4バイトも必要ですが…。

    2018年3月29日 13:18