#!/usr/bin/env python3 """生成 tuku 档案册(CBZ)集成测试夹具:真实 zipfile 产物 + 定点字节修补。 用法: python3 make_cbz_samples.py 产出: /library/ 书库夹具(文件夹册 + 档案册混排) bookA/001.png 002.png 文件夹册(2 页,PNG) vol2.cbz 3 页:PNG(41x31) / PNG(40x30) / 中文名 PNG(12x34) + 非图片、目录项、0 字节条目(都应被过滤) Vol10.cbz 1 页 BMP(7x9),stored box/box1.cbz box2.cbz 只含档案册的子目录(书库行应写 "2 books") notabook.zip 只有文本 → 不是书 emptypages.cbz 目录项 + 文本 → 不是书 broken.cbz 非 zip 字节 → 不是书 truncated.cbz vol2 砍半(EOCD 丢失) → 不是书 .hidden.cbz 隐藏名 → 不参与 /features/ pathsafe.cbz 安全/不安全条目混排(只应收 2 个安全图片页) cjk.cbz 中文名自然序(第1页 < 第2页 < 第10页) big.cbz 80 页 8x8 PNG(解压缓存 LRU 上限用) bomb.cbz CD 声明单页 96MiB(应被过滤,防 zip 炸弹) /manifest.txt 每档案每条目一行:<档案相对路径>\t<条目名>\t\t 图片内容都是结构合法的 PNG/BMP/JPEG 头(PNG 是真图),C 测试用假解码器, 只校验解压字节与 manifest 的 CRC/尺寸一致、以及 meta 探测出的原始尺寸。 """ import os import struct import sys import zipfile import zlib DATE = (2024, 1, 1, 0, 0, 0) # ---------------------------------------------------------------- 图片构造 def png_bytes(w, h, seed=0): raw = bytearray() for y in range(h): raw.append(0) # filter: none for x in range(w): raw += bytes(((x * 7 + y * 13 + seed) & 0xFF, (x * 3 + y * 5 + seed) & 0xFF, (x + y + seed) & 0xFF)) def chunk(tag, data): return (struct.pack(">I", len(data)) + tag + data + struct.pack(">I", zlib.crc32(tag + data) & 0xFFFFFFFF)) ihdr = struct.pack(">IIBBBBB", w, h, 8, 2, 0, 0, 0) # 8-bit RGB return (b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", ihdr) + chunk(b"IDAT", zlib.compress(bytes(raw), 9)) + chunk(b"IEND", b"")) def bmp_bytes(w, h): stride = (w * 3 + 3) & ~3 px = bytes(((i * 11) & 0xFF) for i in range(stride * h)) hdr = b"BM" + struct.pack("H", 16) + b"JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00") dqt = b"\xFF\xDB" + struct.pack(">H", 67) + b"\x00" + bytes(64) sof = b"\xFF\xC0" + struct.pack(">HBHHB", 11, 8, h, w, 1) + b"\x01\x11\x00" return b"\xFF\xD8" + app0 + dqt + sof + b"\xFF\xD9" # ---------------------------------------------------------------- zip 构造 def write_zip(path, entries): """entries: [(name, data, zipfile 压缩方式)]""" with zipfile.ZipFile(path, "w") as z: for name, data, method in entries: zi = zipfile.ZipInfo(name, DATE) zi.compress_type = method z.writestr(zi, data) def find_cd_record(data, name): eocd = data.rfind(b"PK\x05\x06") if eocd < 0: raise SystemExit("generator: no EOCD") total, _cd_size, cd_off = struct.unpack_from(" 64MiB 上限 with open(bomb, "wb") as f: f.write(bytes(data)) raw = open(os.path.join(feat, "pathsafe.cbz"), "rb").read() with open(os.path.join(feat, "truncated.cbz"), "wb") as f: f.write(raw[:len(raw) // 2]) def write_manifest(ro): """每档案每条目:<相对路径>\t<条目名>\t\t(Tab 分隔,UTF-8 原名)。""" lines = [] for base, _dirs, files in os.walk(ro): for name in sorted(files): if not name.lower().endswith((".cbz", ".zip")): continue path = os.path.join(base, name) rel = os.path.relpath(path, ro) try: with zipfile.ZipFile(path) as z: for info in z.infolist(): if info.is_dir(): continue payload = z.read(info) lines.append("%s\t%s\t%08x\t%d" % ( rel, info.filename, zlib.crc32(payload) & 0xFFFFFFFF, len(payload))) except zipfile.BadZipFile: continue with open(os.path.join(ro, "manifest.txt"), "w", encoding="utf-8") as f: f.write("\n".join(lines) + "\n") def main(): if len(sys.argv) != 2: raise SystemExit("usage: make_cbz_samples.py ") ro = sys.argv[1] os.makedirs(ro, exist_ok=True) build_library(ro) build_features(ro) write_manifest(ro) if __name__ == "__main__": main()