奥村晴彦先生のar002およびしんき氏のLHACCESSライブラリのソースの一部をJavaScriptに移植し、レベル2ヘッダおよびlh6に対応させたものです。JavaScriptだけでLZH形式の書庫を解凍します。
Webブラウザが以下の機能をサポートしている必要があります。一部のブラウザでは、サポートしていない機能をブラウザ固有のhackで補っている場合があります。
JavaScriptから任意ファイルへアクセスするために使用しています。Internet ExplorerではネイティブXMLHTTPがあっても使わない場合があります(詳細は後述)。
JavaScriptからバイナリファイルへアクセスするために使用しています。Internet ExplorerがサポートしているresponseBodyは、XMLHttpRequest Level 2の古い草案に含まれていましたが、現在は削除されています。またIE8以前ではJScriptでそのデータへアクセスする手段がなかったため、VBScriptを併用しています。(2011-04-08) 以前はAscB(MidB())で1バイトずつ切り出す方法を使っていたのですが、ちょっとファイルが大きくなると(たった100KB程度でも)遅すぎる上メモリを食いつぶして動作が不安定になり、まったく使いものにならないので、CStr()でJavaScriptからアクセス可能な型にキャストする方法に変更したら1500〜5000倍ほど高速になったようです。ADODB.Recordsetを使ってもっと速くできるようですが、ActiveXフィルターが有効だと使えませんし十分速くなったので今回は採用しませんでした。(2011-09-20追記、2013-07-23更新) JS/Linuxのソースなどで書かれているように、IE9以降ではnew VBArray(xhr.responseBody).toArray();とする方法もあります。IE11 Preview(のIE11モード)ではVBScriptが使えず、overrideMimeType法も通用しない(メソッドはサポートしているがバイナリファイルの読み取りには使えない)ので、この方法を採用しています。ただしこの方法はIE9以降ならIE8以前のドキュメントモードでも使えますが、本物のIE8では使えません(TypeErrorが投げられます)。
Gecko/WebKitはresponseBodyをサポートしていませんが、overrideMimeTypeメソッドによるhackを使用してバイナリデータを読み取ります。
Operaは10.10までどちらもサポートしていなかったため(overrideMimeTypeは存在していましたが呼び出しても何も起きませんでした)、overrideMimeType法をサポートしたバージョン10.50以降が必要です。
Internet Explorerは、ネイティブXMLHttpRequestではローカルファイルからの読み取りができません。このため、IEでは(厳密にはActiveXObjectが存在したら)ローカルファイルへのアクセスにActiveXObject("Msxml2.XMLHTTP")を使うようにしています。
(2010-12-04追加、2011-08-25更新) XMLHttpRequest Level 2の2011年8月版の草案にバイナリファイルアクセスのためのプロパティとしてresponseとresponseTypeが追加されて、responseBodyは廃止されました。これらはWebKit、Gecko 6、IE 10、Opera 11.60がサポートしています。なお、IE 10はネイティブXMLHTTPでのみサポートしています。ActiveXObjectオブジェクト版に仕様の変更はありません。response/responseTypeのプロトタイプ的機能として、responseBlobを少し古いWebKitが、responseArrayBufferをmozResponseArrayBufferという名前でGecko 2.0からGecko 5までサポートしていましたが、これらは使用していません。
遅くてもたぶん動作はしますが実用にならないでしょう。
複数の解凍を並行して実行することは考慮していません。たとえばリンクをクリックしたときイベントに応答して解凍を始めるような処理をしている場合、何も考えずに実装するとユーザーが最初のリンクをクリック後、解凍が終わる前に別のリンクをクリックしたときにおかしくなります。
解凍したデータは8bitバイトの列とみなして文字コードを考慮せず文字列に変換するので、日本語のテキストファイルは文字化けします。
Internet Explore 7以降のバージョンはネイティブXMLHTTPをサポートしていますが、これはローカルファイルやdata:スキームのURIにはとにかく一切アクセスできないようです(MSHTAですら!)。仕方がないので、file:スキームでは同等のActiveXObject("Msxml2.XMLHTTP")を代わりに使用しています。(2015-08-27)Microsoft EdgeではActiveXのサポートが廃止されたので、ローカルファイルに対してXMLHTTPでアクセスする方法は一切ないようです。
ローカルのHTMLを開いた場合、デフォルトではファイルがローカルゾーン(マイコンピュータゾーン)で開かれるため、ローカルコンピューターゾーンのロックダウンによりそもそもスクリプトが実行できません。情報バーから毎回許可するか、インターネットオプションの詳細設定で許可するか、ファイルがインターネットゾーンで開かれるようにする必要があります。
インターネットゾーンでActiveXObjectを使った場合も、ローカルファイルへのアクセスは厳しく制限されています。ZoneIdを付けたファイルはインターネットゾーンにあるとみなされますが、HTMLのみならず関連するファイルすべて(.js、アクセス対象のバイナリファイルなど)にZoneIdを付ける必要があります。同じ理由からHTMLファイルにしか付けられないMark of the Webは役に立ちません。
自分のみがアクセスできる共有フォルダを作成して共有フォルダ経由でアクセスした場合、いちいち関連するファイルすべてにZoneIdを付ける必要はありません。Windows XPではこれが一番容易な方法だと思われますが、Windows Vista/7では共有フォルダの所属するゾーンで保護モードが有効だと共有フォルダ上のファイルから読み取りを行えないようです(HTMLさえ読めません)。インターネットゾーンではデフォルトで保護モードが有効です。インターネットゾーンで保護モードを無効にするのは勧められないので、該当の共有フォルダを信頼済みサイトに追加するなどの対処が必要になります。
Windows XPではTemporary Internet Files、Windows Vista/7ではそれに加えて%USERPROFILE%\AppData\LocalLow配下にファイル一式をコピーして開くとすべてのファイルが自動的にインターネットゾーンにあるとみなされる上保護モードで問題なくアクセスできるので簡単ですが、配置が制限されます。
Windows Vista/7ではLocalLow配下に限らず、整合性レベルが「低」のファイルは自動的にインターネットゾーンにあるとみなされるようです。コマンドプロンプトからicacls . /T /setintegritylevel (CI)(OI)L
を実行すると、カレントディレクトリとその配下のすべてのファイルとディレクトリに対して整合性レベル「低」を設定し、ディレクトリ配下で新しく作成されるファイルの整合性レベルも自動的に「低」となります。これはNTFSボリューム上でありさえすれば配置の制限もありません。Windows XPには整合性レベルがないので、この方法は使えません。(2011-04-08) IE9のActiveXフィルターが有効な場合、整合性レベルを「低」にしたローカルファイルでもActiveXコントロールは無効にされます。しかもリモートファイルと違って一時的に許可することもできません。IEではネイティブXMLHTTPでローカルファイルを読むことはできませんから、ローカルファイルから読み取る手段がなくなります。IE9ではスクリプトの実行許可が簡単になったので、どうしてもローカルファイルでテストする必要がある場合整合性レベルは変更しないほうがいいでしょう。
Firefox 3.0以降のデフォルトでは、ローカルのHTMLからXMLHttpRequestを使って読めるのは同じディレクトリ(またはそのサブディレクトリ)にあるファイルだけです。この制限を回避するにはabout:configで「security.fileuri.strict_origin_policy」をfalseにする必要があります。HTTPでアクセスするWebページからは同一ドメインのファイルを読む場合にはこのような制限はありません。
Google Chrome 5.0以降のデフォルトでは、XMLHttpRequestでローカルのファイルを一切読めません。この制限を回避するには、「--allow-file-access-from-files」オプションを指定して起動する必要があります。HTTPでアクセスするWebページから同一ドメインのファイルを読む場合にはこのような制限はありません。
以上のように、ローカルファイルを開いて動作確認するのは異様に手間がかかるのであまりおすすめしません。素直にローカルサーバを立ててhttp:スキーム上でテストするのが、Internet ExplorerでネイティブXMLHTTPも使えて結果的に一番近道かもしれません。
LZH解凍のコードは、レベル2ヘッダおよびlh6に対応させた以外はほぼ奥村晴彦先生のar002そのまんまですが、JavaScriptへ移植するために以下のような変更を加えています(いくつかは前記リンク先にある2003年版では修正済みです)。
JavaScriptのビット演算は内部的に32bit整数として行われますが(ECMA 262 第3版 11.7 Bitwise Shift Operators)、sizeof bitbufを4にすると処理系の実装によっては遅くなることがあるらしいので(参考)、あえて2にしてbitbufを16bit整数として使っています(実際に速さが変わるかどうかは計ってないけど。bitbuf = (bitbuf << n) & 0xFFFF;
で結局doubleへの変換が行われることがあるから意味ない気もするし。(2015-12-12追記)あとlh1の解凍ルーチンはbitbufが16ビットであることに依存しているように見える)。この場合16bit整数に対するビット演算をシミュレートするために、fillbuf関数内に2ヵ所ある左シフトの演算結果を0xFFFFでマスクする必要があります。
make_table関数内で16bit整数に対してシフト演算しているところも同様です。こちらはbitbufのサイズにかかわらず、ushortへのキャストや代入を行っている箇所でマスクが必要です。
bitbufを16bit整数として使っている場合算術右シフトと論理右シフトの違いが問題になることはないのですが、いちおう右シフト演算子を>>>
に置き換えます。
32bit整数を右に32bitシフトすると、何もしなかった場合と同じ結果になります。奥村先生による解説ではJavaの言語仕様について言及がありますが、ECMAScriptも同様です(上記ECMA-262 第3版 11.7を参照)。bitbufを16bit整数として使っている場合にはgetbitsの修正は不要です。
C言語では
weight[i++] = 1U << (16 - i);
の振る舞いは未定義です。処理系は特定の振る舞いを保証してもかまいませんし、しなくてもかまいません。また振る舞い(またはそれが保証されないこと)について文書化してもかまいませんし、しなくてもかまいません。さらにこのような式を検出して実行時エラーで停止したり、コンパイルを拒否したりする処理系があったとしてもC言語の仕様には違反しません(C FAQの3章と11.33〜11.35あたりを参照のこと)。
C言語ライクな文法を持っていても最近の言語ではさすがに振る舞いを規定していることが多いようですが、結果はかならずしもar002のソースで期待されていたとおりになりません。たとえばECMAScriptでは左辺を(値の更新も含めて)評価してから右辺の評価に移るので、右辺でi
を再度評価したときの値は左辺の評価前より1つ大きくなります(ECMA 262 3rd edition 11.3.1 Postfix Increment Operator、11.13.1 Simple Assignment ( = ))。したがってECMA-262に正しく準拠している限り、いかなる処理系でも間違った結果になることが保証されるので、JavaScriptに移植する場合にも
weight[i] = 1 << (16 - i); i++;
のように修正が必要です。
ファイルシステム上に解凍を行っていないので(そもそもWebブラウザからローカルファイルに保存するのは困難ですが)、指定外の場所にファイルが解凍される脆弱性の影響は受けません。
ar002にはいくつかメモリアクセスにまつわる脆弱性が見つかっていますが、いずれもJavaScriptで任意コード実行の攻撃につながることはないので、とくに対策はしていません。
lh1の解凍部分はLHACCESSライブラリのライセンスに従います。それ以外はar002のライセンスに従います。
772434 - Blob support for Zip file contents Firefox 17から、Zip書庫内のファイルをBlobとして読み出すArchive APIが標準でサポートされます。Firefox 30から事実上廃止
zip (deflate圧縮メソッド) のエンコーダー/デコーダーはそれなりにJS実装が存在するのですが、日本独自のlzhのアーカイバのJS実装はまだ他では見たことがありません。もしご存じの方がいましたらご教示ください。
高度な JavaScript 技集 なんと1999年の作品です。当時のハードウェアやソフトウェアの水準では一種のジョークのようなものだったでしょう。以前はライセンスが不明確だったのですがいつの間にかGPLになっていました。
zlib JavaScript implementation こちらが最新実装のようです。
javascript - でデータを圧縮/伸張する 上記ライブラリを高速化したもの dankogai / js-deflate さらにフォーク→beatgammit / deflate-js
pure JavaScript の Zlib, Deflate 実装を作りました MIT License Zlib Deflate 動作デモ zlib.deflate (fixed huffman) Zlib and Deflate Encode Library zlib.deflate imaya / zlib.js
Inflate 実装を作って PDF.js の凄さを思い知った話 (前編) pdf.jsにもInflateの実装が含まれているようです。
Binary Tools for JavaScript zipのほか、rarとtarのアーカイバもJavaScriptで実装しています。
zlib for JavaScript zlib License
beatrice.js inflateなどが別途必要 デモ: data: URL版 blob: URL版