Files
MeowOS-SDK/tuku/logic/zip/zip.c
T

494 lines
14 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* zip.c — zip 容器读取器实现,见 zip.h 的用法/线程/安全说明。 */
#define _POSIX_C_SOURCE 200809L
#define _FILE_OFFSET_BITS 64
#include "zip.h"
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zlib.h>
#define MZ_SIG_LOCAL 0x04034b50u
#define MZ_SIG_CD 0x02014b50u
#define MZ_SIG_EOCD 0x06054b50u
#define MZ_LOCAL_MIN 30u
#define MZ_CD_MIN 46u
#define MZ_EOCD_MIN 22u
#define MZ_COMMENT_MAX 65535u
#define MZ_IN_CHUNK 65536u
#define MZ_CRC_CHUNK (1u << 24)
struct mz_archive {
FILE *fp;
uint64_t file_size;
int count;
mz_entry *entries;
char *names;
};
static uint16_t rd16(const uint8_t *p)
{
return (uint16_t)((uint16_t)p[0] | ((uint16_t)p[1] << 8));
}
static uint32_t rd32(const uint8_t *p)
{
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) |
((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
}
static int u64_add(uint64_t a, uint64_t b, uint64_t *out)
{
if (a > UINT64_MAX - b) return 0;
*out = a + b;
return 1;
}
static int seek_to(FILE *fp, uint64_t off)
{
off_t o = (off_t)off;
if ((uint64_t)o != off) return MZ_ERR_UNSUPPORTED;
return fseeko(fp, o, SEEK_SET) == 0 ? MZ_OK : MZ_ERR_IO;
}
static int read_exact(FILE *fp, void *buf, size_t len)
{
if (fread(buf, 1, len, fp) == len) return MZ_OK;
return ferror(fp) ? MZ_ERR_IO : MZ_ERR_TRUNCATED;
}
static int read_at(FILE *fp, uint64_t off, void *buf, size_t len)
{
int rc = seek_to(fp, off);
if (rc != MZ_OK) return rc;
return read_exact(fp, buf, len);
}
static int get_file_size(FILE *fp, uint64_t *size)
{
if (fseeko(fp, 0, SEEK_END) != 0) return MZ_ERR_IO;
off_t end = ftello(fp);
if (end < 0) return MZ_ERR_IO;
*size = (uint64_t)end;
return MZ_OK;
}
/* 从文件尾向前找 EOCD 签名;只接受注释长度不越过文件尾的候选。 */
static int find_eocd(FILE *fp, uint64_t file_size, uint64_t *eocd_off,
uint8_t eocd[MZ_EOCD_MIN])
{
if (file_size < MZ_EOCD_MIN) return MZ_ERR_TRUNCATED;
uint64_t tail_len = file_size;
if (tail_len > (uint64_t)MZ_EOCD_MIN + MZ_COMMENT_MAX)
tail_len = (uint64_t)MZ_EOCD_MIN + MZ_COMMENT_MAX;
uint64_t tail_off = file_size - tail_len;
uint8_t *tail = malloc((size_t)tail_len);
if (!tail) return MZ_ERR_MEM;
int rc = read_at(fp, tail_off, tail, (size_t)tail_len);
if (rc == MZ_OK) {
rc = MZ_ERR_FORMAT;
for (uint64_t i = tail_len - MZ_EOCD_MIN + 1; i-- > 0; ) {
if (rd32(tail + i) != MZ_SIG_EOCD) continue;
uint64_t pos = tail_off + i;
uint64_t end = 0;
if (!u64_add(pos, MZ_EOCD_MIN, &end)) continue;
if (!u64_add(end, rd16(tail + i + 20), &end)) continue;
if (end > file_size) continue;
memcpy(eocd, tail + i, MZ_EOCD_MIN);
*eocd_off = pos;
rc = MZ_OK;
break;
}
}
free(tail);
return rc;
}
/* 解析 central directory;名字先指向 cd 缓冲,成功后在 names_out 里做一份连续副本。 */
static int parse_cd(const uint8_t *cd, uint32_t cd_size, int declared,
mz_entry *entries, char **names_out)
{
const uint8_t *p = cd;
const uint8_t *end = cd + cd_size;
uint64_t names_total = 0;
int n = 0;
int rc = MZ_OK;
while (n < declared) {
if ((size_t)(end - p) < MZ_CD_MIN || rd32(p) != MZ_SIG_CD) {
rc = MZ_ERR_FORMAT;
break;
}
uint16_t flags = rd16(p + 8);
uint16_t method = rd16(p + 10);
uint32_t crc = rd32(p + 16);
uint32_t comp = rd32(p + 20);
uint32_t uncomp = rd32(p + 24);
uint16_t nlen = rd16(p + 28);
uint16_t elen = rd16(p + 30);
uint16_t clen = rd16(p + 32);
uint16_t disk = rd16(p + 34);
uint32_t loff = rd32(p + 42);
uint64_t rec = (uint64_t)MZ_CD_MIN + nlen + elen + clen;
if (rec > (uint64_t)(end - p)) {
rc = MZ_ERR_FORMAT;
break;
}
if (disk != 0) {
rc = MZ_ERR_UNSUPPORTED;
break;
}
if (comp == 0xFFFFFFFFu || uncomp == 0xFFFFFFFFu || loff == 0xFFFFFFFFu) {
rc = MZ_ERR_UNSUPPORTED;
break;
}
mz_entry *e = &entries[n];
e->name = (const char *)(p + MZ_CD_MIN);
e->name_len = nlen;
e->method = method;
e->flags = flags;
e->crc = crc;
e->comp_size = comp;
e->uncomp_size = uncomp;
e->local_offset = loff;
names_total += (uint64_t)nlen + 1;
n++;
p += (size_t)rec;
}
if (rc == MZ_OK) {
if (n != declared) {
rc = MZ_ERR_FORMAT;
} else if (names_total > (uint64_t)SIZE_MAX) {
rc = MZ_ERR_MEM;
} else {
char *arena = malloc((size_t)names_total);
if (!arena) {
rc = MZ_ERR_MEM;
} else {
char *w = arena;
for (int i = 0; i < n; i++) {
memcpy(w, entries[i].name, entries[i].name_len);
w[entries[i].name_len] = '\0';
entries[i].name = w;
w += entries[i].name_len + 1;
}
*names_out = arena;
}
}
}
return rc;
}
static int load_cd(mz_archive *z, uint64_t cd_off, uint32_t cd_size, int declared)
{
uint8_t *cd = malloc(cd_size);
if (!cd) return MZ_ERR_MEM;
z->entries = calloc((size_t)declared, sizeof *z->entries);
if (!z->entries) {
free(cd);
return MZ_ERR_MEM;
}
int rc = read_at(z->fp, cd_off, cd, cd_size);
if (rc == MZ_OK) rc = parse_cd(cd, cd_size, declared, z->entries, &z->names);
if (rc == MZ_OK) {
z->count = declared;
for (int i = 0; i < declared; i++) {
uint64_t end = 0;
if (!u64_add(z->entries[i].local_offset, MZ_LOCAL_MIN, &end) ||
end > z->file_size) {
rc = MZ_ERR_TRUNCATED;
break;
}
}
}
free(cd);
return rc;
}
int mz_open(const char *path, mz_archive **out)
{
if (!out) return MZ_ERR_ARG;
*out = NULL;
if (!path) return MZ_ERR_ARG;
mz_archive *z = calloc(1, sizeof *z);
if (!z) return MZ_ERR_MEM;
z->fp = fopen(path, "rb");
if (!z->fp) {
free(z);
return MZ_ERR_IO;
}
int rc = get_file_size(z->fp, &z->file_size);
uint8_t eocd[MZ_EOCD_MIN];
uint64_t eocd_off = 0;
uint16_t n_total = 0;
uint32_t cd_size = 0;
uint32_t cd_off = 0;
if (rc == MZ_OK) rc = find_eocd(z->fp, z->file_size, &eocd_off, eocd);
if (rc == MZ_OK) {
uint16_t disk_no = rd16(eocd + 4);
uint16_t cd_disk = rd16(eocd + 6);
uint16_t n_disk = rd16(eocd + 8);
n_total = rd16(eocd + 10);
cd_size = rd32(eocd + 12);
cd_off = rd32(eocd + 16);
if (disk_no != 0 || cd_disk != 0) {
rc = MZ_ERR_UNSUPPORTED;
} else if (n_total == 0xFFFFu || n_disk == 0xFFFFu ||
cd_size == 0xFFFFFFFFu || cd_off == 0xFFFFFFFFu) {
rc = MZ_ERR_UNSUPPORTED; /* zip64:EOCD 字段饱和 */
} else if (n_disk != n_total) {
rc = MZ_ERR_FORMAT;
} else {
uint64_t cd_end = 0;
if (!u64_add(cd_off, cd_size, &cd_end) || cd_end > eocd_off) {
rc = MZ_ERR_TRUNCATED;
} else if (n_total > 0 && (uint64_t)cd_size < (uint64_t)n_total * MZ_CD_MIN) {
rc = MZ_ERR_FORMAT;
} else if ((uint64_t)cd_size > (uint64_t)SIZE_MAX) {
rc = MZ_ERR_UNSUPPORTED;
}
}
}
if (rc == MZ_OK && n_total > 0) rc = load_cd(z, cd_off, cd_size, n_total);
if (rc != MZ_OK) {
free(z->entries);
free(z->names);
fclose(z->fp);
free(z);
return rc;
}
*out = z;
return MZ_OK;
}
void mz_close(mz_archive *z)
{
if (!z) return;
if (z->fp) fclose(z->fp);
free(z->entries);
free(z->names);
free(z);
}
int mz_count(const mz_archive *z)
{
return z ? z->count : 0;
}
const mz_entry *mz_entry_at(const mz_archive *z, int index)
{
if (!z || index < 0 || index >= z->count) return NULL;
return &z->entries[index];
}
const mz_entry *mz_find(const mz_archive *z, const char *name)
{
if (!z || !name) return NULL;
for (int i = 0; i < z->count; i++) {
if (strcmp(z->entries[i].name, name) == 0) return &z->entries[i];
}
return NULL;
}
int mz_entry_is_dir(const mz_entry *e)
{
return e && e->name_len > 0 && e->name[e->name_len - 1] == '/';
}
static uint32_t crc_buf(const uint8_t *p, size_t n)
{
uLong c = crc32(0L, Z_NULL, 0);
while (n > 0) {
uInt chunk = n > MZ_CRC_CHUNK ? MZ_CRC_CHUNK : (uInt)n;
c = crc32(c, p, chunk);
p += chunk;
n -= chunk;
}
return (uint32_t)c;
}
/* 从 data_off 流式 inflate comp_size 字节到 buf(容量 buf_cap = 声明解压尺寸)。
* 输出多于声明尺寸或流提前结束都算 FORMAT/TRUNCATED,不整块读取压缩数据。 */
static int inflate_entry(FILE *fp, uint64_t data_off, uint64_t comp_size,
uint8_t *buf, size_t buf_cap, size_t *out_len)
{
z_stream z;
memset(&z, 0, sizeof z);
if (inflateInit2(&z, -MAX_WBITS) != Z_OK) return MZ_ERR_MEM;
uint8_t *in = malloc(MZ_IN_CHUNK);
if (!in) {
inflateEnd(&z);
return MZ_ERR_MEM;
}
int rc = seek_to(fp, data_off);
size_t produced = 0;
uint64_t consumed = 0;
uint8_t scratch = 0;
int stalls = 0;
while (rc == MZ_OK) {
if (z.avail_in == 0) {
if (consumed >= comp_size) {
rc = MZ_ERR_TRUNCATED;
break;
}
uint64_t remain = comp_size - consumed;
size_t want = remain > MZ_IN_CHUNK ? MZ_IN_CHUNK : (size_t)remain;
rc = read_exact(fp, in, want);
if (rc != MZ_OK) break;
z.next_in = in;
z.avail_in = (uInt)want;
consumed += want;
}
uInt avail;
if (produced < buf_cap) {
size_t room = buf_cap - produced;
z.next_out = buf + produced;
avail = room > UINT_MAX ? UINT_MAX : (uInt)room;
} else {
z.next_out = &scratch; /* 声明尺寸之外的输出 → 直接判坏流 */
avail = 1;
}
z.avail_out = avail;
uInt in_before = z.avail_in;
int zr = inflate(&z, Z_NO_FLUSH);
size_t made = (size_t)avail - (size_t)z.avail_out;
if (made > buf_cap - produced) {
rc = MZ_ERR_FORMAT;
break;
}
produced += made;
if (zr == Z_STREAM_END) break;
if (zr == Z_OK) {
if (made == 0 && z.avail_in == in_before) {
if (++stalls > 8 || (consumed >= comp_size && z.avail_in == 0)) {
rc = MZ_ERR_TRUNCATED;
break;
}
} else {
stalls = 0;
}
continue;
}
if (zr == Z_BUF_ERROR && z.avail_in == 0 && consumed >= comp_size) {
rc = MZ_ERR_TRUNCATED;
break;
}
rc = MZ_ERR_FORMAT;
break;
}
inflateEnd(&z);
free(in);
if (rc == MZ_OK) *out_len = produced;
return rc;
}
int mz_read(mz_archive *z, const mz_entry *e, void *buf, size_t buf_size,
size_t *out_size)
{
if (out_size) *out_size = 0;
if (!z || !z->fp || !e) return MZ_ERR_ARG;
if (!buf && buf_size > 0) return MZ_ERR_ARG;
if (e->flags & 1u) return MZ_ERR_UNSUPPORTED;
if (e->method != 0 && e->method != 8) return MZ_ERR_UNSUPPORTED;
if (e->uncomp_size > (uint64_t)SIZE_MAX) return MZ_ERR_UNSUPPORTED;
size_t need = (size_t)e->uncomp_size;
if (need > buf_size) return MZ_ERR_BUF;
uint8_t lh[MZ_LOCAL_MIN];
int rc = read_at(z->fp, e->local_offset, lh, sizeof lh);
if (rc != MZ_OK) return rc;
if (rd32(lh) != MZ_SIG_LOCAL) return MZ_ERR_FORMAT;
if (rd16(lh + 8) != e->method) return MZ_ERR_FORMAT;
uint64_t data_off = 0;
uint64_t data_end = 0;
if (!u64_add(e->local_offset, MZ_LOCAL_MIN, &data_off) ||
!u64_add(data_off, rd16(lh + 26), &data_off) ||
!u64_add(data_off, rd16(lh + 28), &data_off) ||
!u64_add(data_off, e->comp_size, &data_end) ||
data_end > z->file_size) {
return MZ_ERR_TRUNCATED;
}
if (e->method == 0) {
if (e->comp_size != e->uncomp_size) return MZ_ERR_FORMAT;
if (need > 0) {
rc = read_at(z->fp, data_off, buf, need);
if (rc != MZ_OK) return rc;
}
if (crc_buf((const uint8_t *)buf, need) != e->crc) return MZ_ERR_CRC;
} else {
size_t made = 0;
rc = inflate_entry(z->fp, data_off, e->comp_size, (uint8_t *)buf, need, &made);
if (rc != MZ_OK) return rc;
if (made != need) return MZ_ERR_FORMAT;
if (crc_buf((const uint8_t *)buf, made) != e->crc) return MZ_ERR_CRC;
}
if (out_size) *out_size = need;
return MZ_OK;
}
int mz_read_alloc(mz_archive *z, const mz_entry *e, void **out_buf,
size_t *out_size)
{
if (!out_buf || !out_size) return MZ_ERR_ARG;
*out_buf = NULL;
*out_size = 0;
if (!z || !e) return MZ_ERR_ARG;
if (e->uncomp_size > (uint64_t)SIZE_MAX) return MZ_ERR_UNSUPPORTED;
size_t need = (size_t)e->uncomp_size;
uint8_t *p = malloc(need ? need : 1);
if (!p) return MZ_ERR_MEM;
size_t n = 0;
int rc = mz_read(z, e, p, need, &n);
if (rc != MZ_OK) {
free(p);
return rc;
}
*out_buf = p;
*out_size = n;
return MZ_OK;
}
const char *mz_strerror(int err)
{
switch (err) {
case MZ_OK: return "ok";
case MZ_ERR_IO: return "I/O error";
case MZ_ERR_FORMAT: return "malformed archive";
case MZ_ERR_TRUNCATED: return "archive truncated or offset out of bounds";
case MZ_ERR_UNSUPPORTED: return "unsupported zip feature";
case MZ_ERR_BUF: return "output buffer too small";
case MZ_ERR_MEM: return "out of memory";
case MZ_ERR_ARG: return "invalid argument";
case MZ_ERR_CRC: return "CRC mismatch";
default: return "unknown error";
}
}