0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pythonでのzipファイルの破損チェック方法

0
Posted at

zipファイルの破損チェックが役立つ場面

自動ダウンロード時のデータ欠落対策

外部でのデータを自動でダウンロードし、そのデータをシステムに取り込むことがあると思います。ダウンロード時に何らかの理由でzipファイルが破損した場合、データを取り込む前にチェックを行えば中途半端なデータや書き換えられたデータを取り込んでしまうことを防げます。

ユーザがアップロードしたファイルの安全性確保

ユーザ入力のzipファイルが問題ないかの検証に使用できます。

Pythonでのzipファイルの破損チェック方法

使用する関数

どちらもPython標準ライブラリであるzipfileの関数です。

zipfile.is_zipfile(filename)

公式ドキュメント

filename が正しいマジックナンバをもつ ZIP ファイルの時に True を返し、そうでない場合 False を返します。filename にはファイルやファイルライクオブジェクトを渡すこともできます。

以下を検出できます。

  • zip形式でないファイル
  • 0Byteのzipファイル

メリット

  • 解凍しないでチェックを行うため処理速度が速い

デメリット

  • zipの形をしているかという外側しかチェックできない

ZipFile.testzip()

公式ドキュメント

アーカイブ内のすべてのファイルを読み込み、それらのCRC(巡回冗長検査)とファイルヘッダーをチェックします。破損している最初のファイルの名称を返します。問題がなければ None を返します。

以下を検出できます。

  • 圧縮されたデータの一部が書き換わっている
  • 圧縮データ内の一部のファイルが欠落している

メリット

  • 実際にデータをすべて解凍してデータの整合性チェックを行うため見逃しが少ない

デメリット

  • 解凍を行うため処理に時間がかかる

実際に検証

前提条件

  • Python3.13

サンプルコード

check_zip_file.py
import os
import zipfile


def check_zip_file(file_path):
    """指定されたZIPファイルの破損チェックを行う関数"""

    # 正しいZIP形式かどうかの事前チェック
    if not zipfile.is_zipfile(file_path):
        print(f"Error: このファイルは有効なZIP形式ではないか、完全に破損しています。")
        return False

    try:
        # ZIPファイルを開く
        with zipfile.ZipFile(file_path, "r") as zf:
            # testzip() は破損した最初のファイルの名称を返す(問題なければ None を返す)
            bad_file = zf.testzip()

            if bad_file is not None:
                print(f"Warning: ZIPファイルが破損しています。")
                print(f"   最初にエラーが検出された内部ファイル: {bad_file}")
                return False
            else:
                print(f"Success: ZIPファイルの破損はありません。")
                return True

    except zipfile.BadZipFile:
        print("Error: ZIPファイル構造が致命的に破損しています。")
        return False
    except Exception as e:
        print(f"Error: 予期せぬエラーが発生しました: {e}")
        return False

if __name__ == "__main__":
    target_zip = "example.zip"
    result = check_zip_file(target_zip)
    if result:
        print("zipファイル破損チェック結果:ZIPファイルは正常です。")
    else:
        print("zipファイル破損チェック結果:破損が検出されました。")

サンプルコード実行結果

項番 対象ファイル 実行結果 破損・異常の検出箇所
1 0Byteのzipファイル Error: このファイルは有効なZIP形式ではないか、完全に破損しています。 is_zipfile()の段階でエラーを検出
2 別形式を.zipに偽装したファイル Error: このファイルは有効なZIP形式ではないか、完全に破損しています。 is_zipfile()の段階でエラーを検出
3 ヘッダを改ざんしたzipファイル Warning: ZIPファイルが破損しています。 is_zipfile()の段階でエラーを検出
4 データの一部を書き換えたファイル Warning: ZIPファイルが破損しています。 testzip()で破損した内部ファイルtest/test1.txtの読み出し(解凍)時にエラーを検出
5 パスワード付きzipファイル Error: 予期せぬエラーが発生しました: File 'test/test1.txt' is encrypted, password required for extraction testzip()で暗号化された内部ファイル test/test1.txtの読み出し(解凍)時にエラーを検出

補足

以下コードで破損したzipファイルを作成。

ヘッダの改ざん

create_bad_header_zip_file.py
def create_bad_header_zip_file():
    with open("normal.zip", "rb") as f:
        data = bytearray(f.read())

    if len(data) > 4:
        data[0:4] = b"FAKE"  # 先頭の「PK..」を消し去る

    with open("bad_header.zip", "wb") as f:
        f.write(data)

データの書き換え

create_bad_ccr_zip_file.py
def create_bad_ccr_zip_file():
    with open("normal.zip", "rb") as f:
        data = bytearray(f.read())

    if len(data) > 100:
        data[100] = 0xFF  # ファイルの真ん中あたりの1バイトを適当に書き換える

    with open("bad_crc.zip", "wb") as f:
        f.write(data)

まとめ

実務で使用するタイミングがあったのでまとめてみました。
testzip()は処理に時間はかかりますが、確実に破損チェックを行いたい場合有効だなと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?