618 lines
22 KiB
Plaintext
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 win32.win32base as wb
|
|
import win32.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] = uint8(v & 0xFF)
|
|
h[1] = uint8((v >> 8) & 0xFF)
|
|
|
|
|
|
def zf_win32le(h: uint8*, v: uint32):
|
|
h[0] = uint8(v & 0xFF)
|
|
h[1] = uint8((v >> 8) & 0xFF)
|
|
h[2] = uint8((v >> 16) & 0xFF)
|
|
h[3] = uint8((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 = 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 = 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(int(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_win32le(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_win32le(hdr + 14, crc)
|
|
zf_win32le(hdr + 18, uint32(plen))
|
|
zf_win32le(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_win32le(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_win32le(hdr + 16, n.crc32)
|
|
zf_win32le(hdr + 20, uint32(n.compress_size))
|
|
zf_win32le(hdr + 24, uint32(n.file_size))
|
|
zf_w16le(hdr + 28, uint32(n.name_len))
|
|
zf_win32le(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_win32le(e, 0x06054B50)
|
|
zf_w16le(e + 8, uint32(self.info_count))
|
|
zf_w16le(e + 10, uint32(self.info_count))
|
|
zf_win32le(e + 12, uint32(cd_size))
|
|
zf_win32le(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
|