hatora's blog

日常の出来事や仕事のことなど/Java/開発/Web/IT

Apache Commons CompressとZIP64

問題発生→調査→原因→解決までの備忘録


クラウド上にあるファイルをまとめて圧縮(今回はzip)するような機能を
提供することになり、今回はCommons Compress(1.5)を採用。

リリース後、「Lhaplusで解凍できない!!」といった問い合わせがあったので調査。


やったこと順番

  1. Commons Compressのソース読む → ようかわらん
  2. zipの仕様確認 → ようわからん
  3. Antで生成したzipがLhaplusで解凍できた → ん??
  4. Ant・Compressのzipのバイナリデータ確認 → ん??
  5. zipの仕様確認 → ん??
  6. zipのバイナリデータ確認 → 拡張フィールドの値おかしくないか??
  7. 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に書いてありました。。。