CLDC,MIDP,MEXAでJarInflater第1回 zipフォーマットについて学ぶ

DoJaのJarInflaterが便利すぎるのでS!アプリでも同様のクラスを使いたい。
ところがCLDC,MIDP,MEXAにはそのようなクラスは用意されていません。


ないなら作ればいいじゃない!


というわけで同様の機能を持ったクラスを作成しましょう。
jarといってもただのzipですので、zipの展開を行います。


汎用的にzipを展開するためにはさまざまなケースに考慮しなければなりませんが、
携帯アプリでリソースのアーカイブとして使う程度であれば限定的な対応でも良いでしょう。
・話を簡単にするため、JDKのjarコマンドで生成したjarファイルの展開に限定します。
・zipの作成は扱いません。
・zip64、暗号化されたものも取り扱いません。
・圧縮アルゴリズムはdeflateのみとし、無圧縮には対応しません。
・zipの展開の足がかりとして参考にしてくださいまし。

zipとは

zipは書庫を扱うフォーマットであって複数のファイル、ディレクトリを取りまとめているだけです。
もちろん圧縮されていないファイルを格納することも可能です。
jarコマンドでは-0オプションで無圧縮zipを作成することができます。


MEXAにはInflateInputStreamというzlibでdeflate形式に圧縮されたストリームを伸長するクラスがあるので圧縮ファイルの伸長はこれに任せませう。


次回ソースを載せますので説明は大雑把ですよ。
zipフォーマットについて詳しく知りたい方は最後のリンクを参照してください。


まずzipはヘッダとそれに続くデータをひとつのブロックとしたブロックの集合になっています。
今回は4種類のヘッダとデータ構成がわかれば展開できますので順に見ていきましょう。

ローカルファイルヘッダ シグネチャ:PK34(0x04034b50)

ヘッダにはオプションフラグ、ファイルの日時、crc、圧縮前のファイルサイズ、圧縮後のファイルサイズなどが
定義されています。
このヘッダに続いてファイル名、拡張データ、ファイルデータが定義されます。
※オプションの4番目のビットが立っている場合crc、圧縮前のファイルサイズ、圧縮後のファイルサイズが0となります。

データディスクリプタ シグネチャ:PK78(0x08074b50)

ローカルファイルヘッダでcrc、サイズが未定義の場合このヘッダが定義され、こちらにファイルサイズなどが記されます。
jarを生成する場合、圧縮したデータを書き出した後でしか圧縮されたデータのサイズを知るすべがないので、
ローカルファイルヘッダまで出力のストリームを戻さなくても済むようになっています。
※jarコマンドで生成したzipアーカイブはこのヘッダを出力します。
※このヘッダはシグネチャが定義されないことがあるようですが、jarコマンドで生成したzipアーカイブでは定義されるようです(JDK6.0)

セントラルディレクトシグネチャ:PK12(0x02014b50)

このヘッダはローカルファイルヘッダのそれと同じ情報を保持しています。
jarコマンドで生成したzipアーカイブでは、crc、ファイルサイズ共に正規の値が定義されています。(ZIPフォーマット的にはそれが正しい?)
ファイルデータは含みません。
対応するローカルファイルヘッダの位置が記録されています。

セントラルディレクトリ終了ヘッダ シグネチャ:PK56(0x06054b50)

セントラルディレクトリの終了を表すヘッダ。
セントラルディレクトリの位置が記録されています。



アーカイブ内のファイルを探索する場合、ローカルファイルヘッダを順に読み目的のファイルを探すのは困難です。
ローカルファイルヘッダのファイル部分のサイズが未定義の場合、続くデータディスクリプタまで読み進めなければファイル部分のサイズがわかりません。
この場合データディスクリプタにたどり着くにはシグネチャが一致するまでファイルを読み進める必要があります。
ところがファイル部分にシグネチャと一致するデータが現れる可能性があるために、ヘッダの正当性を確かめる必要があり、処理が煩雑になってしまうことが予想されます。


そこでセントラルディレクトリを活用します。
セントラルディレクトリはファイルデータを持たないので順にヘッダを読み取ることが可能です。
セントラルディレクトリに目的のファイル名を持つものが見つかれば、同一ファイルのローカルファイルヘッダに移動しファイルデータを読み出せます。
セントラルディレクトリの先頭を見つけるために、まずセントラルディレクトリ終了ヘッダを見つけます。


セントラルディレクトリ終了ヘッダを見つけるために、シグネチャが一致するまでファイルを読み進めます。
さらに正規のセントラルディレクトリ終了ヘッダとして読み進めてファイルの終端に達するかどうかでヘッダの正当性を検証することが出来ます。
(検証は今回は省いています。うまくいかないデータが見つかったとしても携帯アプリのリソースという用途なので差し替えれば済みます)

処理の流れのまとめ

・今回の実装では、まずJarInflater生成時にセントラルディレクトリ終了ヘッダを探索、
続いてセントラルディレクトリへ移動します。
アーカイブされたファイルの探索ではセントラルディレクトリから目的のファイルを見つけ、ローカルファイルヘッダへ移動します。そしてローカルファイルヘッダのファイルデータからInflateInputStreamを作成します。


InflateInputStreamでデータを伸長する場合適切なヘッダとフッタを付加する必要があります。
ヘッダはdeflateの圧縮に関するオプション情報、フッタはデータのCRC(未確認)です。
zlibが生成する正しいdeflateのヘッダとフッタを付加する必要がありますが、簡単にするためjarコマンドで生成したアーカイブの圧縮データが伸長できるヘッダを付加します。
フッタについては付加せずとも正しく伸長できたので無視します。
zlib/deflateを既定の設定で作成すると、ヘッダには[0x78], [0x9C]が出力されます。
おそらくjarコマンドではzlib/deflateの規定値の設定を用いていると思われます。
ヘッダは圧縮のアルゴリズムや圧縮率、スライド窓の大きさ?の定義なので(正しくはzlibのdeflate.cあたり読んだりすると良いです)
このあたりの設定はローカルファイルヘッダ等にも記録されているはずで、復元は可能だとは思いますが0x78,0x9C固定で行きたいと思います。


実装の都合上jarファイルはByteArrayInputStreamで提供されることを前提にしています。
次回はソースを掲載します。

第2回目

リンク

ZIPフォーマットの正式な情報はこちらを参照してください
http://www.pkware.com/documents/casestudies/APPNOTE.TXT

参考
TNKソフトウェア - 私的ZIPファイル研究所