Files
Viper_VRTE/zipfile.vp
T
TermiNexus c0d56b127d chore(comments): includes 头注释英文化(R11 收尾)
把 includes 下 64 个 .vp 的文件头注释(含 traceback.vp 第 13-427 行的整块文件级说明)以及 2 处漏网的正文中文注释(ast/lexer.vp、json/__writer.vp)与 1 处中文 docstring(memhub.vp)逐行译为英文;同步把 fltk/gen_ffi.py 的生成模板与其自身注释英文化,使重新生成仍为英文。

只改注释/文档串文字:行数与代码逐字节不变(骨架比对 140 文件漂移 0),运行时中文字符串字面量 305 处保持原样。验证:三重重编 SUCCESS(170.21s)+ 全量 77/77 顺序回归通过 + VET 编译 SUCCESS / --search-paths 7 条 exit 0。
2026-10-06 09:42:25 +08:00

618 lines
22 KiB
Plaintext

# -*- coding: utf-8 -*-
# zipfile.vp - ZIP archive read/write module (subset of Python zipfile)
#
# Supports:
# - writing members: ZIP_STORED / ZIP_DEFLATED (raw deflate, wbits=-15, CRC32 check)
# - reading members: auto-detects ZIP_STORED / ZIP_DEFLATED, checks CRC32 after decompression
# - on read, scans back from the end of the file for EOCD (tolerates a comment/leading data)
#
# Not supported: zip64 (>4GB / more than 65535 members), encryption, spanning, data descriptors (flag bit 3), etc.
#
# Member names are raw UTF-8 bytes (flag bit 11 is set when writing, not auto-decoded when reading).
#
# Usage (aligned with Python semantics; read-only mode reads the whole zip into memory automatically):
# w: zipfile.ZipFile* = zipfile.ZipFile("out.zip", "w")
# w.write("a.txt", buf, buf_len) # defaults to ZIP_DEFLATED
# w.close()
#
# r: zipfile.ZipFile* = zipfile.ZipFile("out.zip", "r")
# olen: sizet = 0
# data: bytes = r.read("a.txt", &olen) # malloc buffer, free after use
# err = zipfile.error_code() / zipfile.error()
import stdlib
import string
import viperlib
import w32.win32base as wb
import w32.win32file as wf
import zlib.pyzlib as zpy
# ============================================================
# constants
# ============================================================
define ZIP_STORED = 0
define ZIP_DEFLATED = 8
define ZIP_LEVEL_DEFAULT = 6 # compression level (zlib default 6)
define _ZIP_FLAG_UTF8 = 0x0800 # filename in UTF-8 (flag bit 11)
define _ZIP_VERSION = 20 # version needed 2.0
define _ZIP_DOS_DATE_EPOCH = 0x21 # 1980-01-01
define _ZIP_MAX_COMMENT = 65535
# error codes (method return values; for global last error see error_code()/error())
define ZIP_ERR_OK = 0
define ZIP_ERR_OPEN = -1
define ZIP_ERR_NOT_ZIP = -2
define ZIP_ERR_MEMORY = -3
define ZIP_ERR_NOT_FOUND = -4
define ZIP_ERR_INVALID = -5
define ZIP_ERR_UNSUPPORTED = -6
define ZIP_ERR_CRC = -7
define ZIP_ERR_IO = -8
define ZIP_ERR_MODE = -9
define ZIP_ERR_CLOSED = -10
# ============================================================
# module-level error state
# ============================================================
zipfile_error_code_val: int = 0
zipfile_error_msg: [256]char = ""
def zipfile_set_error(code: int, msg: str):
global zipfile_error_code_val, zipfile_error_msg
zipfile_error_code_val = code
if msg:
string.strncpy(zipfile_error_msg, msg, 255)
zipfile_error_msg[255] = '\0'
else:
zipfile_error_msg[0] = '\0'
def zipfile_set_error_win(code: int, msg: str, err: ulong):
global zipfile_error_code_val, zipfile_error_msg
zipfile_error_code_val = code
if err != 0:
viperlib.snprintf(zipfile_error_msg, 256, "%s (win32 err=%u)", msg, err)
else:
string.strncpy(zipfile_error_msg, msg, 255)
zipfile_error_msg[255] = '\0'
def error_code() -> int:
return zipfile_error_code_val
def error() -> str:
return zipfile_error_msg
# ============================================================
# small utilities
# ============================================================
def zf_strlen(s: bytes) -> sizet:
n: sizet = 0
while s is not None and *s != 0:
n += 1
s += 1
return n
def zf_dup_name(src: bytes, length: sizet) -> bytes:
buf: bytes = bytes(stdlib.malloc(length + 1))
if not buf: return None
if length > 0: string.memcpy(buf, src, length)
buf[length] = 0
return buf
def zf_u16le(p: uint8*) -> uint32:
return uint32(p[0]) | (uint32(p[1]) << 8)
def zf_u32le(p: uint8*) -> uint32:
return (uint32(p[0]) | (uint32(p[1]) << 8) | (uint32(p[2]) << 16) | (uint32(p[3]) << 24))
def zf_w16le(h: uint8*, v: uint32):
h[0] = v & 0xFF
h[1] = (v >> 8) & 0xFF
def zf_w32le(h: uint8*, v: uint32):
h[0] = v & 0xFF
h[1] = (v >> 8) & 0xFF
h[2] = (v >> 16) & 0xFF
h[3] = (v >> 24) & 0xFF
# All file openings allow read/write/delete sharing, avoiding mutually exclusive handles
define _ZF_SHARE = 7 # FILE_SHARE_READ|WRITE|DELETE
def zf_share_flags() -> ulong:
return ulong(_ZF_SHARE)
def zf_write_all(h: wb.HANDLE, p: uint8*, n: sizet) -> int:
done: sizet = 0
while done < n:
chunk: sizet = n - done
if chunk > 0xFFFFFF00:
chunk = 0xFFFFFF00
written: ulong = 0
ok: int = wf.WriteFile(h, p + done, ulong(chunk), &written, None)
if ok == 0 or written == 0:
global zipfile_error_code_val, zipfile_error_msg
zipfile_error_code_val = ZIP_ERR_IO
viperlib.snprintf(zipfile_error_msg, 256, "write failed (win32_err=%u)", wb.GetLastError())
return ZIP_ERR_IO
done += sizet(written)
return ZIP_ERR_OK
def zf_read_all(h: wb.HANDLE, p: uint8*, n: sizet) -> int:
done: sizet = 0
while done < n:
chunk: sizet = n - done
if chunk > 0xFFFFFF00:
chunk = 0xFFFFFF00
bytes_read: ulong = 0
ok: int = wf.ReadFile(h, p + done, ulong(chunk), &bytes_read, None)
if ok == 0 or bytes_read == 0:
global zipfile_error_code_val, zipfile_error_msg
zipfile_error_code_val = ZIP_ERR_IO
viperlib.snprintf(zipfile_error_msg, 256,
"read failed (win32_err=%u)", wb.GetLastError())
return ZIP_ERR_IO
done += sizet(bytes_read)
return ZIP_ERR_OK
def zf_read_whole(path: str, out_len: sizet*) -> uint8*:
if not out_len:
zipfile_set_error(ZIP_ERR_OPEN, "out_len is None")
return None
h: wb.HANDLE = wf.CreateFileA(
path, wf.GENERIC_READ, zf_share_flags(),
None, wf.OPEN_EXISTING, wf.FILE_ATTRIBUTE_NORMAL, None)
if h == wb.INVALID_HANDLE_VALUE:
zipfile_set_error_win(ZIP_ERR_OPEN, "cannot open file for reading", wb.GetLastError())
return None
li: wb.LARGE_INTEGER = wb.LARGE_INTEGER()
ok_size: int = wf.GetFileSizeEx(h, &li)
if ok_size == 0:
wb.CloseHandle(h)
zipfile_set_error(ZIP_ERR_IO, "cannot get file size")
return None
sz: sizet = sizet(li.QuadPart)
if sz == 0:
wb.CloseHandle(h)
zipfile_set_error(ZIP_ERR_NOT_ZIP, "empty file is not a zip archive")
return None
buf: bytes = bytes(stdlib.malloc(sz))
if not buf:
wb.CloseHandle(h)
zipfile_set_error(ZIP_ERR_MEMORY, "out of memory")
return None
rc: int = zf_read_all(h, buf, sz)
wb.CloseHandle(h)
if rc != ZIP_ERR_OK:
stdlib.free(buf)
return None
*out_len = sz
return buf
# ============================================================
# Entry info (central directory item). Nodes are explicit calloc allocations, owned and
# freed by ZipFile (avoids GC reclaiming instances referenced via Ptr fields).
# ============================================================
class ZipInfo:
filename: bytes # raw byte name (incl \0; owned and freed by zipfile)
name_len: sizet
compress_type: int # ZIP_STORED / ZIP_DEFLATED
crc32: uint32
compress_size: sizet
file_size: sizet
header_offset: sizet # local file header offset
next: ZipInfo* # linked list (keeps write/directory order)
# ============================================================
# ZipFile
# ============================================================
class ZipFile:
path: bytes
mode: int = -1 # 0 = read, 1 = write
compression: int # compression used when write() does not specify one
fh: wb.HANDLE = None # write-mode handle (raw win32 CreateFileA)
data: bytes = None # read mode: in-memory copy of whole archive
data_len: sizet = 0
info_head: ZipInfo* = None
info_tail: ZipInfo* = None
info_count: sizet = 0
pos: sizet = 0 # write-mode current offset
ok: int = 0 # 1 = usable
closed: int = 0
def __init__(self, path: str, mode: str = "r", compression: int = ZIP_DEFLATED):
self.path = path
self.compression = compression
# Note: strings must be compared with strcmp (== compares pointers only).
if string.strcmp(mode, "r") == 0: self.mode = 0
elif string.strcmp(mode, "w") == 0: self.mode = 1
else:
zipfile_set_error(ZIP_ERR_OPEN, "mode must be 'r' or 'w'")
self.closed = 1
return
if self.mode == 0:
self.data = zf_read_whole(path, &self.data_len)
if not self.data:
self.closed = 1
return
if self._parse() != ZIP_ERR_OK:
stdlib.free(self.data)
self.data = None
self.data_len = 0
self._clear_infos()
self.closed = 1
return
else:
self.fh = wf.CreateFileA(
path, wf.GENERIC_WRITE, zf_share_flags(),
None, wf.CREATE_ALWAYS, wf.FILE_ATTRIBUTE_NORMAL, None)
if self.fh == wb.INVALID_HANDLE_VALUE:
zipfile_set_error_win(ZIP_ERR_OPEN, "cannot create file for writing", wb.GetLastError())
self.fh = None
self.closed = 1
return
self.pos = 0
self.ok = 1
def _clear_infos(self):
n: ZipInfo* = self.info_head
while n is not None:
if n.filename is not None:
stdlib.free(n.filename)
n.filename = None
nxt: ZipInfo* = n.next
n.next = None
stdlib.free(n)
n = nxt
self.info_head = None
self.info_tail = None
self.info_count = 0
def _append_info(self) -> ZipInfo*:
node: ZipInfo* = stdlib.calloc(1, ZipInfo.__sizeof__())
if not node:
zipfile_set_error(ZIP_ERR_MEMORY, "out of memory")
return None
node.next = None
if self.info_tail is None:
self.info_head = node
else:
self.info_tail.next = node
self.info_tail = node
self.info_count += 1
return node
# ---- Read mode: parse central directory ----
def _parse(self) -> int:
if self.data_len < 22:
zipfile_set_error(ZIP_ERR_NOT_ZIP, "file too small to be a zip")
return ZIP_ERR_NOT_ZIP
d: bytes = self.data
# Scan back from tail for EOCD (tolerates trailing comment)
max_back: sizet = self.data_len - 22
if max_back > _ZIP_MAX_COMMENT + 20:
max_back = _ZIP_MAX_COMMENT + 20
eocd: sizet = 0
found: int = 0
i: sizet = 0
while i <= max_back and found == 0:
p: sizet = self.data_len - 22 - i
if d[p] == 0x50 and d[p + 1] == 0x4B and d[p + 2] == 0x05 and d[p + 3] == 0x06:
eocd = p
found = 1
i += 1
if found == 0:
zipfile_set_error(ZIP_ERR_NOT_ZIP, "EOCD not found")
return ZIP_ERR_NOT_ZIP
total: uint32 = zf_u16le(d + eocd + 10)
cd_off: uint32 = zf_u32le(d + eocd + 16)
cd_size: uint32 = zf_u32le(d + eocd + 12)
if sizet(cd_off) + sizet(cd_size) > self.data_len:
zipfile_set_error(ZIP_ERR_NOT_ZIP, "central directory out of range")
return ZIP_ERR_NOT_ZIP
cp: sizet = sizet(cd_off)
end: sizet = sizet(cd_off) + sizet(cd_size)
for idx in range(total):
if cp + 46 > end:
zipfile_set_error(ZIP_ERR_NOT_ZIP, "central directory entry truncated")
return ZIP_ERR_NOT_ZIP
if zf_u32le(d + cp) != 0x02014B50:
zipfile_set_error(ZIP_ERR_NOT_ZIP, "bad central directory signature")
return ZIP_ERR_NOT_ZIP
method: uint32 = zf_u16le(d + cp + 10)
crc: uint32 = zf_u32le(d + cp + 16)
csize: uint32 = zf_u32le(d + cp + 20)
usize: uint32 = zf_u32le(d + cp + 24)
nlen: uint32 = zf_u16le(d + cp + 28)
elen: uint32 = zf_u16le(d + cp + 30)
clen: uint32 = zf_u16le(d + cp + 32)
hoff: uint32 = zf_u32le(d + cp + 42)
rec_len: sizet = 46 + sizet(nlen) + sizet(elen) + sizet(clen)
if cp + rec_len > end:
zipfile_set_error(ZIP_ERR_NOT_ZIP, "central directory entry out of range")
return ZIP_ERR_NOT_ZIP
if nlen == 0:
zipfile_set_error(ZIP_ERR_NOT_ZIP, "central directory entry has empty name")
return ZIP_ERR_NOT_ZIP
dup: bytes = zf_dup_name(d + cp + 46, sizet(nlen))
if not dup:
zipfile_set_error(ZIP_ERR_MEMORY, "out of memory")
return ZIP_ERR_MEMORY
node: ZipInfo* = self._append_info()
if node is None:
stdlib.free(dup)
return ZIP_ERR_MEMORY
node.filename = dup
node.name_len = sizet(nlen)
node.compress_type = int(method)
node.crc32 = crc
node.compress_size = sizet(csize)
node.file_size = sizet(usize)
node.header_offset = sizet(hoff)
cp = cp + rec_len
return ZIP_ERR_OK
# ---- Public queries ----
def namelist_at(self, i: sizet) -> bytes:
if self.ok == 0 or self.info_count == 0 or i >= self.info_count:
return None
n: ZipInfo* = self.info_head
k: sizet = 0
while n is not None:
if k == i:
return n.filename
n = n.next
k += 1
return None
def getinfo(self, name: str) -> ZipInfo*:
if self.ok == 0:
return None
n: ZipInfo* = self.info_head
while n is not None:
if n.filename is not None and string.strcmp(n.filename, name) == 0:
return n
n = n.next
return None
# ---- Read member: decompress + CRC32 check ----
def read(self, name: str, out_len: sizet*) -> bytes:
if not out_len:
zipfile_set_error(ZIP_ERR_INVALID, "out_len is None")
return None
*out_len = 0
if self.closed or self.ok == 0:
zipfile_set_error(ZIP_ERR_CLOSED, "zip file is closed")
return None
if self.mode != 0:
zipfile_set_error(ZIP_ERR_MODE, "zip file is not open for reading")
return None
info: ZipInfo* = self.getinfo(name)
if info is None:
zipfile_set_error(ZIP_ERR_NOT_FOUND, "member not found")
return None
d: bytes = self.data
off: sizet = sizet(info.header_offset)
if off + 30 > self.data_len:
zipfile_set_error(ZIP_ERR_NOT_ZIP, "local file header out of range")
return None
if zf_u32le(d + off) != 0x04034B50:
zipfile_set_error(ZIP_ERR_NOT_ZIP, "bad local file header signature")
return None
nlen: uint32 = zf_u16le(d + off + 26)
elen: uint32 = zf_u16le(d + off + 28)
start: sizet = off + 30 + sizet(nlen) + sizet(elen)
csz: sizet = sizet(info.compress_size)
if start + csz > self.data_len:
zipfile_set_error(ZIP_ERR_NOT_ZIP, "member data out of range")
return None
payload: bytes = d + start
out: bytes = None
ctype: int = info.compress_type
if ctype == ZIP_STORED:
out = bytes(stdlib.malloc(sizet(info.file_size) + 1))
if not out:
zipfile_set_error(ZIP_ERR_MEMORY, "out of memory")
return None
if csz > 0:
string.memcpy(out, payload, csz)
*out_len = sizet(info.file_size)
elif ctype == ZIP_DEFLATED:
olen: sizet = 0
out = zpy.decompress(None, payload, csz, -15, 0, &olen)
if not out:
zipfile_set_error(ZIP_ERR_IO, "deflate decompression failed")
return None
*out_len = olen
else:
zipfile_set_error(ZIP_ERR_UNSUPPORTED, "unsupported compression method")
return None
got_len: sizet = *out_len
if got_len != sizet(info.file_size):
stdlib.free(out)
*out_len = 0
zipfile_set_error(ZIP_ERR_NOT_ZIP, "member size mismatch")
return None
crc_calc: uint32 = uint32(zpy.zlib_crc32(out, got_len, 0))
if crc_calc != info.crc32:
stdlib.free(out)
*out_len = 0
zipfile_set_error(ZIP_ERR_CRC, "CRC32 mismatch")
return None
return out
# ---- Write member ----
def write(self, arcname: str, data: bytes, data_len: sizet,
compress_type: int = -1) -> int:
if self.closed or self.ok == 0:
zipfile_set_error(ZIP_ERR_CLOSED, "zip file is closed")
return ZIP_ERR_CLOSED
if self.mode != 1:
zipfile_set_error(ZIP_ERR_MODE, "zip file is not open for writing")
return ZIP_ERR_MODE
if not arcname:
zipfile_set_error(ZIP_ERR_INVALID, "arcname is required")
return ZIP_ERR_INVALID
ctype: int = compress_type
if ctype == -1:
ctype = self.compression
if ctype != ZIP_STORED and ctype != ZIP_DEFLATED:
zipfile_set_error(ZIP_ERR_UNSUPPORTED, "compress_type must be ZIP_STORED or ZIP_DEFLATED")
return ZIP_ERR_UNSUPPORTED
if data_len > 0 and not data:
zipfile_set_error(ZIP_ERR_INVALID, "data is None but data_len > 0")
return ZIP_ERR_INVALID
name_len: sizet = zf_strlen(arcname)
if name_len == 0:
zipfile_set_error(ZIP_ERR_INVALID, "arcname is empty")
return ZIP_ERR_INVALID
if name_len > 0xFFFF or data_len > 0xFFFFFFFF:
zipfile_set_error(ZIP_ERR_UNSUPPORTED, "name or data too large for zip32")
return ZIP_ERR_UNSUPPORTED
crc: uint32 = uint32(zpy.zlib_crc32(data, data_len, 0))
payload: bytes = data
plen: sizet = data_len
if ctype == ZIP_DEFLATED:
tmp: sizet = 0
payload = zpy.compress(None, data, data_len, ZIP_LEVEL_DEFAULT, -15, &tmp)
if not payload:
zipfile_set_error(ZIP_ERR_IO, "deflate compression failed")
return ZIP_ERR_IO
plen = tmp
hoff: sizet = self.pos
hdr: bytes = bytes(stdlib.malloc(30))
if not hdr:
if ctype == ZIP_DEFLATED:
stdlib.free(payload)
zipfile_set_error(ZIP_ERR_MEMORY, "out of memory")
return ZIP_ERR_MEMORY
string.memset(hdr, 0, 30)
zf_w32le(hdr, 0x04034B50)
zf_w16le(hdr + 4, _ZIP_VERSION)
zf_w16le(hdr + 6, _ZIP_FLAG_UTF8)
zf_w16le(hdr + 8, uint32(ctype))
zf_w16le(hdr + 12, _ZIP_DOS_DATE_EPOCH)
zf_w32le(hdr + 14, crc)
zf_w32le(hdr + 18, uint32(plen))
zf_w32le(hdr + 22, uint32(data_len))
zf_w16le(hdr + 26, uint32(name_len))
rc: int = zf_write_all(self.fh, hdr, 30)
stdlib.free(hdr)
if rc == ZIP_ERR_OK:
rc = zf_write_all(self.fh, arcname, name_len)
if rc == ZIP_ERR_OK:
rc = zf_write_all(self.fh, payload, plen)
if ctype == ZIP_DEFLATED:
stdlib.free(payload)
if rc != ZIP_ERR_OK:
return rc
self.pos = self.pos + 30 + name_len + plen
dup: bytes = zf_dup_name(arcname, name_len)
if not dup:
zipfile_set_error(ZIP_ERR_MEMORY, "out of memory")
return ZIP_ERR_MEMORY
node: ZipInfo* = self._append_info()
if node is None:
stdlib.free(dup)
return ZIP_ERR_MEMORY
node.filename = dup
node.name_len = name_len
node.compress_type = ctype
node.crc32 = crc
node.compress_size = plen
node.file_size = data_len
node.header_offset = hoff
return ZIP_ERR_OK
# ---- Write-mode finish: central directory + EOCD ----
def _finish_write(self) -> int:
if self.info_count > _ZIP_MAX_COMMENT:
zipfile_set_error(ZIP_ERR_UNSUPPORTED, "too many entries (>65535)")
return ZIP_ERR_UNSUPPORTED
cd_start: sizet = self.pos
hdr: bytes = stdlib.malloc(46)
if not hdr:
zipfile_set_error(ZIP_ERR_MEMORY, "out of memory")
return ZIP_ERR_MEMORY
rc: int = ZIP_ERR_OK
n: ZipInfo* = self.info_head
while n is not None and rc == ZIP_ERR_OK:
string.memset(hdr, 0, 46)
zf_w32le(hdr, 0x02014B50)
zf_w16le(hdr + 4, _ZIP_VERSION) # version made by
zf_w16le(hdr + 6, _ZIP_VERSION) # version needed
zf_w16le(hdr + 8, _ZIP_FLAG_UTF8)
zf_w16le(hdr + 10, uint32(n.compress_type))
zf_w16le(hdr + 14, _ZIP_DOS_DATE_EPOCH)
zf_w32le(hdr + 16, n.crc32)
zf_w32le(hdr + 20, uint32(n.compress_size))
zf_w32le(hdr + 24, uint32(n.file_size))
zf_w16le(hdr + 28, uint32(n.name_len))
zf_w32le(hdr + 42, uint32(n.header_offset))
rc = zf_write_all(self.fh, hdr, 46)
if rc == ZIP_ERR_OK:
rc = zf_write_all(self.fh, n.filename, n.name_len)
if rc == ZIP_ERR_OK:
self.pos = self.pos + 46 + n.name_len
n = n.next
stdlib.free(hdr)
if rc != ZIP_ERR_OK:
return rc
cd_size: sizet = self.pos - cd_start
e: bytes = stdlib.malloc(22)
if not e:
zipfile_set_error(ZIP_ERR_MEMORY, "out of memory")
return ZIP_ERR_MEMORY
string.memset(e, 0, 22)
zf_w32le(e, 0x06054B50)
zf_w16le(e + 8, uint32(self.info_count))
zf_w16le(e + 10, uint32(self.info_count))
zf_w32le(e + 12, uint32(cd_size))
zf_w32le(e + 16, uint32(cd_start))
rc = zf_write_all(self.fh, e, 22)
stdlib.free(e)
if rc != ZIP_ERR_OK: zipfile_set_error(ZIP_ERR_IO, "write EOCD failed")
return rc
def close(self) -> int:
if self.closed: return ZIP_ERR_OK
rc: int = ZIP_ERR_OK
if self.mode == 1 and self.fh is not None:
rc = self._finish_write()
wb.CloseHandle(self.fh)
self.fh = None
elif self.mode == 0:
if self.data is not None:
stdlib.free(self.data)
self.data = None
self.data_len = 0
self._clear_infos()
self.closed = 1
self.ok = 0
return rc