Files
codex-market-app/electron/lib/zip.js
T

148 lines
5.9 KiB
JavaScript
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.js —— 零依赖 ZIP 解压(stored/deflate),供技能安装与热更新复用。
"use strict";
const fs = require("fs");
const path = require("path");
const zlib = require("zlib");
// 解析中央目录,把 zip 内容写入 destDir;返回写出的文件数。
function extractZip(buf, destDir) {
// 找 EOCD(0x06054b50),从尾部回扫
let eocd = -1;
for (let i = buf.length - 22; i >= 0 && i >= buf.length - 22 - 65536; i--) {
if (buf.readUInt32LE(i) === 0x06054b50) { eocd = i; break; }
}
if (eocd < 0) throw new Error("不是有效的 zip(找不到 EOCD)");
const count = buf.readUInt16LE(eocd + 10);
let ptr = buf.readUInt32LE(eocd + 16); // 中央目录起始偏移
let written = 0;
for (let n = 0; n < count; n++) {
if (buf.readUInt32LE(ptr) !== 0x02014b50) break;
const method = buf.readUInt16LE(ptr + 10);
const compSize = buf.readUInt32LE(ptr + 20);
const nameLen = buf.readUInt16LE(ptr + 28);
const extraLen = buf.readUInt16LE(ptr + 30);
const commentLen = buf.readUInt16LE(ptr + 32);
const localOff = buf.readUInt32LE(ptr + 42);
const name = buf.toString("utf8", ptr + 46, ptr + 46 + nameLen);
ptr += 46 + nameLen + extraLen + commentLen;
// 读本地头,定位数据
if (buf.readUInt32LE(localOff) !== 0x04034b50) continue;
const lNameLen = buf.readUInt16LE(localOff + 26);
const lExtraLen = buf.readUInt16LE(localOff + 28);
const dataStart = localOff + 30 + lNameLen + lExtraLen;
const comp = buf.subarray(dataStart, dataStart + compSize);
// 防目录穿越
const safe = name.replace(/\\/g, "/").replace(/^\/+/, "");
if (safe.includes("../")) continue;
const outPath = path.join(destDir, safe);
if (safe.endsWith("/")) { fs.mkdirSync(outPath, { recursive: true }); continue; }
fs.mkdirSync(path.dirname(outPath), { recursive: true });
let data;
if (method === 0) data = comp;
else if (method === 8) data = zlib.inflateRawSync(comp);
else throw new Error(`不支持的压缩方式 ${method}`);
fs.writeFileSync(outPath, data);
written++;
}
return written;
}
// ---- 零依赖 ZIP 压缩(deflate/stored),供员工团队打包等复用 ----
// CRC32 表(懒加载)
let CRC_TABLE = null;
function crc32(buf) {
if (!CRC_TABLE) {
CRC_TABLE = new Int32Array(256);
for (let n = 0; n < 256; n++) {
let c = n;
for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
CRC_TABLE[n] = c;
}
}
let crc = ~0;
for (let i = 0; i < buf.length; i++) crc = (crc >>> 8) ^ CRC_TABLE[(crc ^ buf[i]) & 0xff];
return (~crc) >>> 0;
}
// MS-DOS 时间/日期(固定用一个合法值即可,不追求精确时间戳)
function dosDateTime(d) {
const date = d || new Date();
const time = ((date.getHours() & 0x1f) << 11) | ((date.getMinutes() & 0x3f) << 5) | ((date.getSeconds() / 2) & 0x1f);
const day = (((date.getFullYear() - 1980) & 0x7f) << 9) | (((date.getMonth() + 1) & 0x0f) << 5) | (date.getDate() & 0x1f);
return { time: time & 0xffff, date: day & 0xffff };
}
// files: [{ path, content(string|Buffer), mode?(unix 权限,如 0o755) }]
// 返回一个 zip 的 Buffer。目录靠文件路径里的 "/" 隐含,无需单独目录项。
function createZip(files) {
const { time, date } = dosDateTime();
const locals = [];
const centrals = [];
let offset = 0;
for (const f of files) {
const nameBuf = Buffer.from(String(f.path).replace(/^\/+/, ""), "utf8");
const raw = Buffer.isBuffer(f.content) ? f.content : Buffer.from(String(f.content == null ? "" : f.content), "utf8");
const crc = crc32(raw);
// 尝试 deflate;若压不小就存储(method 0)
let method = 8;
let comp = zlib.deflateRawSync(raw, { level: 9 });
if (comp.length >= raw.length) { method = 0; comp = raw; }
// 本地文件头
const lh = Buffer.alloc(30);
lh.writeUInt32LE(0x04034b50, 0);
lh.writeUInt16LE(20, 4); // version needed
lh.writeUInt16LE(0, 6); // flags
lh.writeUInt16LE(method, 8);
lh.writeUInt16LE(time, 10);
lh.writeUInt16LE(date, 12);
lh.writeUInt32LE(crc, 14);
lh.writeUInt32LE(comp.length, 18);
lh.writeUInt32LE(raw.length, 22);
lh.writeUInt16LE(nameBuf.length, 26);
lh.writeUInt16LE(0, 28); // extra len
locals.push(lh, nameBuf, comp);
// 中央目录项
const ch = Buffer.alloc(46);
// unix 权限写进外部属性高 16 位(低位保留),默认 0644,可执行给 0755
const unixMode = f.mode ? f.mode : 0o644;
const externalAttr = (unixMode << 16) >>> 0;
ch.writeUInt32LE(0x02014b50, 0);
ch.writeUInt16LE(0x031e, 4); // version made by (unix)
ch.writeUInt16LE(20, 6); // version needed
ch.writeUInt16LE(0, 8); // flags
ch.writeUInt16LE(method, 10);
ch.writeUInt16LE(time, 12);
ch.writeUInt16LE(date, 14);
ch.writeUInt32LE(crc, 16);
ch.writeUInt32LE(comp.length, 20);
ch.writeUInt32LE(raw.length, 24);
ch.writeUInt16LE(nameBuf.length, 28);
ch.writeUInt16LE(0, 30); // extra len
ch.writeUInt16LE(0, 32); // comment len
ch.writeUInt16LE(0, 34); // disk number
ch.writeUInt16LE(0, 36); // internal attr
ch.writeUInt32LE(externalAttr, 38);
ch.writeUInt32LE(offset, 42);
centrals.push(ch, nameBuf);
offset += lh.length + nameBuf.length + comp.length;
}
const localPart = Buffer.concat(locals);
const centralPart = Buffer.concat(centrals);
const eocd = Buffer.alloc(22);
eocd.writeUInt32LE(0x06054b50, 0);
eocd.writeUInt16LE(0, 4);
eocd.writeUInt16LE(0, 6);
eocd.writeUInt16LE(files.length, 8);
eocd.writeUInt16LE(files.length, 10);
eocd.writeUInt32LE(centralPart.length, 12);
eocd.writeUInt32LE(localPart.length, 16); // central dir offset
eocd.writeUInt16LE(0, 20);
return Buffer.concat([localPart, centralPart, eocd]);
}
module.exports = { extractZip, createZip };