Pythonでrarファイルの解凍をおこなうサンプル

Page content

Pythonでrarファイルの解凍をおこなう

rarfileライブラリを利用

import os
import rarfile


def safe_extract_rar(
    rar_filepath,
    output_dir,
    max_total_size_mb=4096,
    max_file_size_mb=1024 
):
    """
    RARファイルを安全に展開する

    引数:
        rar_filepath (str): RARファイルのパス
        output_dir (str): 展開先のディレクトリ
        max_total_size_mb (int): 合計ファイルサイズの上限 (MB)
        max_file_size_mb (int): ファイル単位のサイズ上限 (MB)
    戻り値:
        bool: 全てのファイルが正常に展開された場合はTrue、それ以外はFalse
    """
    # 展開先の絶対パスを取得し、存在しない場合は作成
    safe_output_dir = os.path.abspath(output_dir)
    os.makedirs(safe_output_dir, exist_ok=True)

    max_total_bytes = max_total_size_mb * 1024 * 1024
    # 【追加】ファイル単位の上限をバイトに変換
    max_single_file_bytes = max_file_size_mb * 1024 * 1024
    total_size = 0
    is_success = True

    try:
        with rarfile.RarFile(rar_filepath) as rf:
            for member in rf.infolist():
                # (ファイル名デコード、パス、シンボリックリンクのチェック)
                try:
                    filename = member.filename.encode('cp437').decode(
                        'utf-8', 'ignore'
                    )
                except Exception:
                    filename = member.filename

                full_path = os.path.join(safe_output_dir, filename)
                dest_path = os.path.abspath(full_path)
                if not dest_path.startswith(safe_output_dir + os.sep):
                    print(f"警告: 安全でないパスのためスキップ '{filename}'")
                    is_success = False
                    continue

                if member.is_symlink():
                    print(f"警告: シンボリックリンクのためスキップ '{filename}'")
                    is_success = False
                    continue

                if member.is_dir():
                    continue

                # --- ここからサイズチェック ---

                # 【追加】ファイル単位のサイズチェック
                if member.file_size > max_single_file_bytes:
                    err_msg = (
                        f"エラー: ファイル '{filename}' のサイズが上限 "
                        f"{max_file_size_mb} MB を超えています。"
                    )
                    print(err_msg)
                    return False  # 即座にFalseを返して終了

                # 合計サイズチェック
                total_size += member.file_size
                if total_size > max_total_bytes:
                    err_msg = (
                        f"エラー: 合計サイズが上限 {max_total_size_mb} MB "
                        "を超えました。処理を中断します。"
                    )
                    print(err_msg)
                    return False

                # 安全なファイルのみ展開
                print(f"展開中: {filename}")
                rf.extract(member, path=safe_output_dir)

            return is_success

    except FileNotFoundError:
        print(f"エラー: RARファイルが見つかりません '{rar_filepath}'")
        return False
    except rarfile.BadRarFile:
        err_msg = (
            f"エラー: '{rar_filepath}' は不正なRARファイルか、"
            "破損しています。"
        )
        print(err_msg)
        return False
    except rarfile.PasswordRequired:
        print(f"エラー: '{rar_filepath}' はパスワードで保護されています。")
        return False
    except Exception as e:
        print(f"予期せぬエラーが発生しました: {e}")
        return False
  • 相対指定により、展開先より上位ディレクトリに展開されるのを防止
  • 文字化けを極力避けるコードを追加
  • 展開時に巨大化するファイルへの対応

参考