(data: Uint8Array)
| 158 | * which is fine for marketplace zips (~3.5MB) and MCPB bundles. |
| 159 | */ |
| 160 | export function parseZipModes(data: Uint8Array): Record<string, number> { |
| 161 | // Buffer view for readUInt* methods — shares memory, no copy. |
| 162 | const buf = Buffer.from(data.buffer, data.byteOffset, data.byteLength) |
| 163 | const modes: Record<string, number> = {} |
| 164 | |
| 165 | // 1. Find the End of Central Directory record (sig 0x06054b50). It lives in |
| 166 | // the trailing 22 + 65535 bytes (fixed EOCD size + max comment length). |
| 167 | // Scan backwards — the EOCD is typically the last 22 bytes. |
| 168 | const minEocd = Math.max(0, buf.length - 22 - 0xffff) |
| 169 | let eocd = -1 |
| 170 | for (let i = buf.length - 22; i >= minEocd; i--) { |
| 171 | if (buf.readUInt32LE(i) === 0x06054b50) { |
| 172 | eocd = i |
| 173 | break |
| 174 | } |
| 175 | } |
| 176 | if (eocd < 0) return modes // malformed — let fflate's error surface elsewhere |
| 177 | |
| 178 | const entryCount = buf.readUInt16LE(eocd + 10) |
| 179 | let off = buf.readUInt32LE(eocd + 16) // central directory start offset |
| 180 | |
| 181 | // 2. Walk central directory entries (sig 0x02014b50). Each entry has a |
| 182 | // 46-byte fixed header followed by variable-length name/extra/comment. |
| 183 | for (let i = 0; i < entryCount; i++) { |
| 184 | if (off + 46 > buf.length || buf.readUInt32LE(off) !== 0x02014b50) break |
| 185 | const versionMadeBy = buf.readUInt16LE(off + 4) |
| 186 | const nameLen = buf.readUInt16LE(off + 28) |
| 187 | const extraLen = buf.readUInt16LE(off + 30) |
| 188 | const commentLen = buf.readUInt16LE(off + 32) |
| 189 | const externalAttr = buf.readUInt32LE(off + 38) |
| 190 | const name = buf.toString('utf8', off + 46, off + 46 + nameLen) |
| 191 | |
| 192 | // versionMadeBy high byte = host OS. 3 = Unix. For Unix zips, the high |
| 193 | // 16 bits of externalAttr hold st_mode (file type + permission bits). |
| 194 | if (versionMadeBy >> 8 === 3) { |
| 195 | const mode = (externalAttr >>> 16) & 0xffff |
| 196 | if (mode) modes[name] = mode |
| 197 | } |
| 198 | |
| 199 | off += 46 + nameLen + extraLen + commentLen |
| 200 | } |
| 201 | |
| 202 | return modes |
| 203 | } |
| 204 | |
| 205 | /** |
| 206 | * Reads a zip file from disk asynchronously and unzips it. |
no test coverage detected