ZIPファイルは非互換性の塊
複数のファイルやフォルダを1個にまとめて、更にサイズの削減までできてしまうZIPファイル。さらに、パスワードを使った暗号化にも対応しています。特にWindows環境の人にとっては、ソフトの配布・メールへの書類の添付などに欠かせない存在になっています。(macOS・Linuxだとtarballの方が一般的だと思います)
最近問題になっているPPAPにも欠かせない存在です。
しかしながら、このZIPファイルには、OS・言語・作成ツールによって、日本語等(非ASCII文字)のエンコーディング方法が異なるというとんでもない問題があります。WindowsやmacOSで日本語等を含んだ名前のファイルから普通にZIPファイルを作成した場合、別のOS(Windowsの場合は別の言語でも)で解凍すると、ファイル名が化けます。
それこそPPAPする場合、受信者が非日本人・マカーだと破綻します。
一応、規格上では、特定のフラグを立ててZIPファイルに埋め込めば、解凍・表示時にUTF-8として扱うというようになっていますが、Windows7以前では無視されていました。さらに、Unicode正規化については何も決められていないので、macOSのFinderでは、普通でない正規化形式のファイル名をしれっと紛れ込ませてきます。さらに、Windowsで一般的な方法や、macOSの zip
コマンドでは、このUTF-8フラグに対応していません。
詳細
Windowsの「送る」→「圧縮 (zip 形式) フォルダー」を使う場合は、Shift-JISでファイル名が保存されます。(日本語の場合)macOSの場合、ターミナルのzip
コマンドは、UTF-8使用フラグが立たないのでWindowsでは正しく取り扱われません。Finderで作成した場合、濁点・半濁点が「付く前の文字」+「濁点or半濁点」のよいうに分解されて保存されます。Windows・Linuxでは再合成されないので、一見何も問題がないように見えても、実際にはファイル選択時等に支障が出ます。
LinuxのZIPコマンドはお行儀がよく、一般的な正規化のUTF-8でファイル名を保存してくれる上、フラグもきちんと立ててくれます。また、PowerShellのCompress-Archive
コマンドレットも、同じくお行儀がよいです。
WindowsのサードパーティZIP作成ソフトも行儀の悪いものだらけです。一昔前に全盛期を誇っていたLhaplusでは当然Shift-JISのみ対応(解凍時にもUTF-8フラグの取扱なし)、7-Zipでも隠しオプション的な扱いをされています。(デフォルトだとShift-JIS、)Windows7への忖度が全ての原因です。
なお、ZIPの規格では、UTF-8使用フラグがついていなければ、ASCII文字以外の使用は認められません。
つくったもの
https://github.com/tats-u/zifu
ZIFUという名前のツールです。ZIP File names to UTF-8の略です。
現在ではコマンドラインツールの形態を取っていますが、いずれ本当に実行を習慣化してほしい情弱の人達をターゲットにできるように、GUI版も作りたいなーと思っています。
ライブラリの選定・開発
以下のようなライブラリが必要でしたが、使えるようなものはありませんでした。
- ZIPファイル中の構造体をローレベルで扱うライブラリ
- macOS独自のUnicode正規化を直すライブラリ
- OEMコードページをUTF-8と相互変換するライブラリ(国際化対応に必要)
- 現在のロケールに対応するANSI/OEMコードページを取得するライブラリ(macOS・Linux用)
それぞれ、以下のようなクレートを作成して公開しました。
ZIPファイルの構造体を作成するライブラリ
https://github.com/tats-u/rust-zip-structs/
https://crates.io/crates/zip_structs
既存のライブラリは、ファイル名が指すバイト配列を変更するという要求に応えられませんでした。
最初はプログラム本体のクレートに同梱していましたが、別クレートに分離しました。
公式の仕様書・日本語の解説資料等を参照しつつ、hexyl
で実際のZIPファイルを解析したりもしました。
macOS独自のUnicode正規化を直すライブラリ
https://github.com/tats-u/rust-hfs-nfd/
https://crates.io/crates/hfs_nfd
このUnicode正規化は非標準のものです。標準のNFDとは一部結果が食い違うため、公式のテーブルを取得して自動でRustコードを生成するスクリプトをPythonで書きました。
OEMコードページをUTF-8と相互変換するライブラリ
https://github.com/tats-u/rust-oem-cp/
https://crates.io/crates/oem_cp
アジア以外の言語のエンコーディングは、ANSIコードページとOEMコードページの2つに分かれています。
ターミナルやZIPファイルではOEMコードページの方が使われます。ANSIの方はライブラリがりますが、OEMの方はなかったので、仕方なく作りました。
多くのコードページは、https://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/PC/に対応表がありましたが、一部欠落していたため、それらはICUに入っていた対応表を仕方なく使用しました。
ロケール文字列中のトークン(ja
・jp
など)をキーとしたハッシュ木を構築して、辿っています。
こちらも自動生成スクリプト使用。
現在のロケールに対応するANSI/OEMコードページを取得するライブラリ
https://github.com/tats-u/locale-name-code-page
https://crates.io/crates/locale_name_code_page
WindowsではコードページはWindows APIのGetACP
・GetOEMCP
で取得できますが、macOS・LinuxではUTF-8が当たり前なので、そうは行きません。
Microsoftが過去に公開していた対応表の魚拓(Wayback Machine)が残っていたので、それからリストを作成しました。
こちらも自動生成スクリプト使用。
実装について
ZIPファイルの大半は、「ローカルファイルヘッダ」「中央ディレクトリ」「中央ディレクトリ終端」の3種類の構造体からなります。ファイル名の情報は前者2つに格納されています。この2つには共通して以下のような情報があります。
- ファイル名のバイト数
- ファイル名がUTF-8で格納されているかどうか
- ファイル名
「ファイル名がUTF-8で格納されているかどうか」が格納されているのは、「汎用目的フラグ」と呼ばれる2バイトの整数です。これをリトルエンディアンで見て、LSBから11ビット目が1になっていれば、UTF-8で格納されています。これは、リトルエンディアンで取得した数値に1 << 11 = 0x0800
をANDして0でない、という条件に置き換えられます。
このフラグが立っているならば、Apple独自のUnicode正規化を行っていないかチェックし、ユーザの同意の元修正します。
立っていない場合、ASCII以外の文字が入っていないかどうかチェックします。入っている場合は、システム言語に対応したエンコーディング(日本語ならShift-JIS)、または与えられたエンコーディングを使用し、UTF-8への変換を試みます。
ファイル名の変更をしたとして、今度はファイルへの保存も一苦労です。変更前後でファイル名の長さが変わり、構造体はファイル名等の分可変長ですので、これらの変更内容だけサクッと書き換え・・・といったことはできません。
構造体の内容は(中身のファイルの内容以外だけでも)保持しておき、新しいファイルを作って逐次書き込まなければなりません。
上書きをする動作の場合は、同じディレクトリに一時ファイルを作る→元のファイルを削除→一時ファイルをリネーム という手段を取りました。
インストール
リポジトリのReleasesからZIP・debファイルをダウンロードして適当に配置(ZIP)・インストール(deb)してください。
ZIPファイルはCPUのアーキテクチャ・OS毎に分かれています。主なものは以下の2つです。
-
x86_64-windows
・x86_64-linux
: Windows・Linuxの場合、通常はこれを選んでください。 -
universal-macos
: Intel CPU・Apple Silicon両対応(のはず)です。
その他は特殊なケース用(ARM版のWindows・Linuxなど)です。
また、Rustにはcrates.ioという非常にありがたい公式リポジトリが存在します。Rust環境があれば、以下のコマンドでインストール完了させることもできます。
cargo install zifu
Rustのインストールは、rustup経由を推奨します。
使い方
以下のコマンドで本ツールの適用が必要かどうかがわかります。
zifu -c 【ZIPファイルのパス】
ファイル名一覧は次のコマンドで確認できます。
zifu -l 【ZIPファイルのパス】
Windows 7以前以外の全環境で扱えるように変換するには、以下のコマンドで可能です。
zifu 【ZIPファイルのパス】 【出力先のパス】
上書きする場合は、代わりに以下のコマンドを入力します。
zifu -i 【ZIPファイルのパス】
免責事項
MITライセンスのため、本ツールを適用して再入手不可のZIPファイルが壊れたなどの責任は負いかねます。また、テストコード等である程度の動作の確認して万全を期していますが、完全な動作保証はありません。特に、ZIP64非対応のため、4GB近くのZIPファイルを扱う際には十分注意してください。
問題がありましたら、IssueやPRをお願いします。