概要
PyPIにパッケージをパブリッシュしようとしたら、以下のようなエラーが出てパブリッシュできなかった。
<html>
<head>
<title>400 Invalid distribution file. File exceeds compression ratio
of 50</title>
</head>
<body>
<h1>400 Invalid distribution file. File exceeds compression ratio of
50</h1>
The server could not comply with the request since it is either
malformed or otherwise incorrect.<br/><br/>
Invalid distribution file. File exceeds compression ratio of 50
</body>
</html>
間接的原因. なぜエラーが発生したのか?
解凍したときにファイルサイズが50倍以上に膨れ上がるzipファイルをアップロードしたことが原因だった。
ちなみに、解凍したらサイズが膨れ上がるようなファイルを利用してシステムをクラッシュさせるような攻撃をZip Bombと呼ぶらしい。全然知らなかった。
46MBが4.5PBへと膨れ上がるZIP爆弾がネット上で公開中
Zip Bombを防ぐために、PyPIでは2023年の6月頃から圧縮率50倍を超えるファイルを含むパッケージのパブリッシュを拒否するようになっているようだ。
最初は圧縮率10倍以上で弾いていたが、制限が厳しすぎるということで50倍以上に緩和されたらしい。
PyPI is now requiring wheels to be too much compressed
Fix ZIP bomb threshold too low
自分の場合は、Python製の麻雀ゲームの向聴数計算部分をC拡張で実装という記事で紹介した通り、もともとPythonで実装したコードをCで実装し直したのだが、そのときに作られたCのバイナリファイルが81.9MBから1.8MBまで圧縮できてしまうことで、PyPIの制限に引っかかってしまった。
ちなみにPyPIの制限はパッケージ全体ではなく、個々のファイルごとにかかっているようだ。
自分の場合はパッケージ全体だと圧縮率は50倍以下で、Cのバイナリファイルだけ50倍を超えているとい感じだ。
直接的原因. なぜ圧縮率が50倍を超えたのか?
Cのコードの中に向聴数を計算するためのテーブルを埋め込んだのが原因だと考えられる。
このテーブルはsuuhai_distance_table.cとzihai_distance_table.cというファイルに定義されていて、それぞれ37.3MBと1.49MBでそこそこ容量が大きい。
難しい説明は省くのだが、このテーブルは仕様の関係で非常にデータの繰り返しが多く、それが原因で圧縮率が高くなってしまっているのだと考えられる。
ちなみにCに書き換える前のPythonのコードでは、元々のテーブルをgzip
モジュールを使って圧縮した状態で保存し、読み出すときに再度gzip
モジュールを使って解凍するという方式にしていたので、Zip Bomb判定はされていなかった。
解決方法
結論から言うと諦めた。
例えば、Pythonで実装していたときと同じように、「テーブルを圧縮した状態で保存しておいて読み出すときに解凍する」という方式にすればこの問題は回避できるんだと思う。
ただ自分はそもそもCが得意じゃないので、そのような複雑な処理をCで書くというのは億劫でやりたくない。
既存のコードを持ってくれば楽かもしれないが、Makefileの書き方もよく分かっていない。
そしてC拡張でファイルを読み出すときはPython側からファイルのパスを渡してあげないとダメらしく、それも面倒でやりたくない。
テーブル自体を.c
として定義しておけば、#include
で読み込むだけで済むので、わざわざファイルのパスをPython側から渡す必要がなくて楽なのだ。
また、テーブルの圧縮率を下げるために圧縮しにくいようなダミーデータを追加するという方法も考えたが、あまりにも非本質的な感じがするのでやめた。
そもそも、このOSSの利用者はおそらく世界中で自分だけだし、pip install git+https://github.com/zurukumo/kago-utils.git
みたいに書いてあげることで、GitHub経由でインストールすることは可能なので「まあ、いっか」と考えた。
というわけでREADME.mdだけ書き換えるコミットを作ってよしとした。
まとめ
妥協 is 大事。
コメント