/* zip.h — 纯 C zip 容器读取器(只依赖 libz 的 inflate/crc32 + stdio)。 * * 定位:tuku 漫画阅读器的 CBZ/zip 容器层——开包、列目录、按需解压单条目。 * 不依赖 meow_ui.h / SDK / tuku 其它逻辑,宿主可裸测(见 tuku/test/test_zip.c)。 * * 用法: * mz_archive *z = NULL; * int rc = mz_open("book.cbz", &z); * if (rc != MZ_OK) { fprintf(stderr, "%s\n", mz_strerror(rc)); return; } * for (int i = 0; i < mz_count(z); i++) { * const mz_entry *e = mz_entry_at(z, i); * if (mz_entry_is_dir(e) || !is_image_name(e->name)) continue; * size_t n = 0; * void *px = NULL; * if (mz_read_alloc(z, e, &px, &n) == MZ_OK) { decode(px, n); free(px); } * } * mz_close(z); * * 线程约束:单个 mz_archive 不是线程安全的(共享 FILE* 位置与条目表只读); * 多线程请各自 mz_open 自己的 archive,或由调用方加锁。模块内无全局可变状态。 * * 内存:mz_open 只把 central directory 的条目元数据(条目表 + 名字副本)读进内存, * 不展开条目数据。mz_read 解压到调用方缓冲(缓冲需 >= 该条目 uncomp_size); * mz_read_alloc 只为单个条目分配一次。 * * 安全:所有偏移/长度都按文件实际大小校验;越界、截断、坏 magic、坏 CRC 一律 * 返回错误码而不崩溃。条目名按原始字节透传(通常是 UTF-8,不做规范化/解码)。 * 名字可能是 ".."、绝对路径或反斜杠——调用方若把它落到文件系统,必须自行拒绝 * 越界路径。加密、zip64、分卷压缩包明确返回 MZ_ERR_UNSUPPORTED。 * * 错误码均为负值;MZ_OK 为 0。mz_strerror 对所有已知码返回静态字符串。 */ #ifndef TUKU_LOGIC_ZIP_ZIP_H #define TUKU_LOGIC_ZIP_ZIP_H #include #include enum { MZ_OK = 0, MZ_ERR_IO = -1, /* 文件读写失败(打开/seek/read) */ MZ_ERR_FORMAT = -2, /* 结构非法:坏 magic、central directory 不一致、流损坏 */ MZ_ERR_TRUNCATED = -3, /* 声明的偏移/长度越过文件尾(含局部头越界) */ MZ_ERR_UNSUPPORTED = -4, /* 加密 / zip64 / 分卷 / 非 stored-deflate 压缩方式 */ MZ_ERR_BUF = -5, /* 调用方缓冲小于条目 uncomp_size */ MZ_ERR_MEM = -6, /* 内存分配失败 */ MZ_ERR_ARG = -7, /* 空指针 / 越界 index 等调用错误 */ MZ_ERR_CRC = -8 /* 解压结果 CRC-32 与 central directory 不符 */ }; typedef struct mz_entry { const char *name; /* NUL 结尾;内容为 zip 内原始字节,生命周期同 archive */ uint32_t name_len; /* 原始名字字节数(不含结尾 NUL,name 中可能有内嵌 NUL) */ uint16_t method; /* 0=stored, 8=deflate;其它值在 mz_read 时返回 UNSUPPORTED */ uint16_t flags; /* zip general purpose flags(bit0=加密,bit11=UTF-8 名) */ uint32_t crc; /* 条目内容 CRC-32 */ uint64_t comp_size; /* 压缩后字节数(archive 内) */ uint64_t uncomp_size; /* 原始字节数 */ uint64_t local_offset; /* local file header 在 archive 内的偏移 */ } mz_entry; typedef struct mz_archive mz_archive; /* 打开 zip。成功时 *out 为新 archive(mz_close 释放);失败时 *out == NULL。 */ int mz_open(const char *path, mz_archive **out); void mz_close(mz_archive *z); int mz_count(const mz_archive *z); const mz_entry *mz_entry_at(const mz_archive *z, int index); /* 越界返回 NULL */ const mz_entry *mz_find(const mz_archive *z, const char *name); /* 精确 strcmp,找不到 NULL */ /* 目录条目(名字以 '/' 结尾)。注意 zip 常见但没有强制要求目录条目存在。 */ int mz_entry_is_dir(const mz_entry *e); /* 解压单条目到 buf。buf_size 必须 >= e->uncomp_size,否则 MZ_ERR_BUF。 * 成功时 *out_size = uncomp_size(可为 0)。out_size 可为 NULL。 * 校验 CRC 与声明尺寸;失败时 buf 内容不可用。 */ int mz_read(mz_archive *z, const mz_entry *e, void *buf, size_t buf_size, size_t *out_size); /* mz_read 的便捷版:malloc 恰好 uncomp_size 字节(0 长度条目分配 1 字节)。 * 成功时 *out_buf 由调用方 free。 */ int mz_read_alloc(mz_archive *z, const mz_entry *e, void **out_buf, size_t *out_size); /* 只解压条目**前缀**到 buf(buf_size = 容量):stored 直接读前 buf_size 字节, * deflate 在 inflate 出 buf_size 字节后即停(不整条目解压)。 * *out_size = 实际写入字节数(= min(buf_size, uncomp_size))。 * 用途:CBZ 排版只看图片头取宽高(PNG IHDR 33 字节 / JPEG SOF / BMP 头都在前 4KB)。 * 注意:没有读完整条目,因此**不校验整段 CRC**(只校验结构/长度),不要用它取内容。 * 出错语义与 mz_read 相同(越界/坏流/加密/zip64 一律返回错误码)。 */ int mz_read_prefix(mz_archive *z, const mz_entry *e, void *buf, size_t buf_size, size_t *out_size); const char *mz_strerror(int err); #endif /* TUKU_LOGIC_ZIP_ZIP_H */