Apache Commons CompressとZIP64
問題発生→調査→原因→解決までの備忘録
クラウド上にあるファイルをまとめて圧縮(今回はzip)するような機能を
提供することになり、今回はCommons Compress(1.5)を採用。
リリース後、「Lhaplusで解凍できない!!」といった問い合わせがあったので調査。
やったこと順番
- Commons Compressのソース読む → ようかわらん
- zipの仕様確認 → ようわからん
- Antで生成したzipがLhaplusで解凍できた → ん??
- Ant・Compressのzipのバイナリデータ確認 → ん??
- zipの仕様確認 → ん??
- zipのバイナリデータ確認 → 拡張フィールドの値おかしくないか??
- Commons Compressのソースを読む → 謎は全て解けた
zipの仕様確認とバイナリデータ確認
これは以下を参考
ZIP (ファイルフォーマット) - Wikipedia
バイナリデータは以下のコマンドで
od -tx1c 〜/test.zip こんな感じでデータが表示 0000000 50 4b 03 04 0a 00 00 08 08 00 77 70 3f 44 51 66 P K 003 004 \n \0 \0 \b \b \0 w p ? D Q f 0000020 df 08 3f 05 00 00 e8 0e 00 00 08 00 00 00 41 41 337 \b ? 005 \0 \0 350 016 \0 \0 \b \0 \0 \0 A A 0000040 41 41 2e 74 78 74 bd 57 4f 6f dc 44 14 3f 7b 3f A A . t x t 275 W O o 334 D 024 ? { ? ※以下は省略
AntとCompressそれぞれで同じデータに対してzipを作成して比較すると、微妙に値が異なる部分が。
※ちなみに、似たような機能をAnt利用で提供していたので比較対象としています。
ファイル名AAAA.txtの2Byte前の部分。
Ant:\0 \0
Compress :024 \0
この2Byteは「拡張フィールドの長さ」を表しており、Compressの方は何らかの拡張フィールドが追加されていたようで、これが原因?
Commons Compressのソースを読む
zipの作成方法はこんなかんじ(リソースクローズ省略)
ZipArchiveOutputStream os = new ZipArchiveOutputStream(new File("test.zip")); File testFile = new File("archived.txt"); os.putArchiveEntry(new ZipArchiveEntry("archived.txt")); IOUtils.copy(new FileInputStream(testFile), os); os.closeArchiveEntry(); os.close();
ZipArchiveOutputStream#putArchiveEntry部分のソースを読んでみると以下の様な記述が。
・ ・ final Zip64Mode effectiveMode = getEffectiveZip64Mode(entry.entry); validateSizeInformation(effectiveMode); if (shouldAddZip64Extra(entry.entry, effectiveMode)) { ・ ・
デバッグした結果、このif文がtrueとなっており、ZIP64の拡張フィールドが追加されていた模様。
つまり、LhaplusはZIP64に対応していなかったためエラーとなっていた。
詳細は省略しますが、今回ZipArchiveEntryのコンストラクタの引数にはエントリー名だけをしていました。
圧縮前のファイルサイズをsetterもしくはコンストラクタで指定しないと、ZIP64モードになるようです。
Antでうまくいったのは、Verが古い状態であったためで、最新の1.9.3で試すと同じ現象を確認できました。
基本的にAntでもCompressでもZIP64が必要(エントリーサイズが65535以上etc)な場合は、自動で対応してくれるみたいです。ただし圧縮前のサイズをエントリーオブジェクトに渡さないと、強制的にZIP64となるというのが今回のオチでした。
この辺はしっかりJavadocに書いてありました。。。