JavaScriptによるLZHの解凍サンプル

これは何?

奥村晴彦先生ar002およびしんき氏のLHACCESSライブラリのソースの一部をJavaScriptに移植し、レベル2ヘッダおよびlh6に対応させたものです。JavaScriptだけでLZH形式の書庫を解凍します。

技術情報

更新履歴

2015-12-12
2015-12-05

システム要件

Webブラウザが以下の機能をサポートしている必要があります。一部のブラウザでは、サポートしていない機能をブラウザ固有のhackで補っている場合があります。

XMLHttpRequest

JavaScriptから任意ファイルへアクセスするために使用しています。Internet ExplorerではネイティブXMLHTTPがあっても使わない場合があります(詳細は後述)。

XMLHttpRequestのresponseBodyもしくはoverrideMimeType

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は廃止されました。これらはWebKitGecko 6、IE 10、Opera 11.60がサポートしています。なお、IE 10はネイティブXMLHTTPでのみサポートしています。ActiveXObjectオブジェクト版に仕様の変更はありません。response/responseTypeのプロトタイプ的機能として、responseBlobを少し古いWebKitが、responseArrayBufferをmozResponseArrayBufferという名前でGecko 2.0からGecko 5までサポートしていましたが、これらは使用していません。

高速なJavaScriptエンジン

遅くてもたぶん動作はしますが実用にならないでしょう。

制限事項

複数の解凍を並行して実行することは考慮していません。たとえばリンクをクリックしたときイベントに応答して解凍を始めるような処理をしている場合、何も考えずに実装するとユーザーが最初のリンクをクリック後、解凍が終わる前に別のリンクをクリックしたときにおかしくなります。

解凍したデータは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も使えて結果的に一番近道かもしれません。

ar002のJavaScriptへの移植

LZH解凍のコードは、レベル2ヘッダおよびlh6に対応させた以外はほぼ奥村晴彦先生ar002そのまんまですが、JavaScriptへ移植するために以下のような変更を加えています(いくつかは前記リンク先にある2003年版では修正済みです)。

セキュリティ

ファイルシステム上に解凍を行っていないので(そもそもWebブラウザからローカルファイルに保存するのは困難ですが)、指定外の場所にファイルが解凍される脆弱性の影響は受けません。

ar002にはいくつかメモリアクセスにまつわる脆弱性が見つかっていますが、いずれもJavaScriptで任意コード実行の攻撃につながることはないので、とくに対策はしていません。

ライセンス

lh1の解凍部分はLHACCESSライブラリのライセンスに従います。それ以外はar002のライセンスに従います。

リンク

772434 - Blob support for Zip file contents Firefox 17から、Zip書庫内のファイルをBlobとして読み出すArchive APIが標準でサポートされます。Firefox 30から事実上廃止


Copyright© 2009-2015 Masatoshi Kimura (emk)