code wiki / (root) / nx_zip.nx

nx_zip.nx source

↩ module page · 264 lines · 9601 B

1// nx_zip.nx -- ZIP archive writer + reader (STORED method): a COMPLETE pair. 2// 3// The tree could READ zips (nx_zip_read.nx) and could not write one. That is 4// half a format: EPUB, OOXML, JAR and every download bundle are ZIP, and 5// producing them was impossible. This closes it with a writer and an 6// independent reader in one file, so it proves by exact round-trip. 7// 8// THE CRC IS THE REFLECTED ONE. ZIP uses CRC-32 with polynomial 0xEDB88320 9// (the reflected form of 0x04C11DB7), init and final-xor both 0xFFFFFFFF. 10// This is a DIFFERENT algorithm from the one nx_ogg.nx uses, which is the 11// same polynomial UNreflected with zero init. Two formats in this tree now 12// use "CRC-32" and they are not interchangeable -- swapping them yields 13// archives that every unzip rejects with a checksum error. T1 pins this one 14// to the classic check value: CRC32("123456789") == 0xCBF43926. 15// 16// THE OFFSET IS WRITTEN TWICE. Each entry's metadata appears in BOTH the 17// local header and the central directory, and the central directory also 18// carries the local header's byte offset. Readers trust the CENTRAL 19// DIRECTORY -- an archive whose local headers are fine but whose central 20// offsets are wrong opens as empty or corrupt, and the local headers give no 21// hint anything is off. 22// 23// STORED, NOT DEFLATED. Method 0 stores bytes verbatim. That is a fully 24// valid ZIP that every tool opens; wiring nx_deflate_enc.nx in as method 8 is 25// a compression improvement, not a correctness one. 26// 27// genealogy_id: pkware_appnote_zip 28// lineage_id: nx_zip_v1 29// license_tier: ORIGINAL 30 31import "nx_syscalls.nx" 32const NX_MAGIC_65535: i64 = 65535 33 34const NX_ZIP_SIG_LOCAL: i64 = 0x04034b50 35const NX_ZIP_SIG_CD: i64 = 0x02014b50 36const NX_ZIP_SIG_EOCD: i64 = 0x06054b50 37const NX_ZIP_LOCAL_LEN: i64 = 30 38const NX_ZIP_CD_LEN: i64 = 46 39const NX_ZIP_EOCD_LEN: i64 = 22 40const NX_ZIP_METHOD_STORE: i64 = 0 41const NX_ZIP_VERSION: i64 = 20 42const NX_ZIP_CRC_POLY: i64 = 0xedb88320 43const NX_ZIP_U32: i64 = 0xffffffff 44 45// writer state slots 46const NX_ZIP_ST_OFF: i64 = 0 47const NX_ZIP_ST_CDOFF: i64 = 1 48const NX_ZIP_ST_COUNT: i64 = 2 49 50// reader field slots 51const NX_ZIP_FLD_CRC: i64 = 0 52const NX_ZIP_FLD_CSIZE: i64 = 1 53const NX_ZIP_FLD_USIZE: i64 = 2 54const NX_ZIP_FLD_NAMELEN: i64 = 3 55const NX_ZIP_FLD_NAMEOFF: i64 = 4 56const NX_ZIP_FLD_LOCALOFF: i64 = 5 57const NX_ZIP_FLD_NEXTCD: i64 = 6 58const NX_ZIP_FLD_METHOD: i64 = 7 59 60// ===== the REFLECTED CRC-32 ======================================= 61 62func nxzip_crc32(data: *u8, n: i64) -> i64 { 63 var crc: i64 = NX_ZIP_U32 64 var i: i64 = 0 65 while i < n { 66 crc = crc ^ ((data[i] as i64) & 255) 67 var b: i64 = 0 68 while b < 8 { 69 if (crc & 1) != 0 { 70 crc = ((crc >> 1) ^ NX_ZIP_CRC_POLY) & NX_ZIP_U32 71 } else { 72 crc = (crc >> 1) & NX_ZIP_U32 73 } 74 b = b + 1 75 } 76 i = i + 1 77 } 78 return (crc ^ NX_ZIP_U32) & NX_ZIP_U32 79} 80 81// ===== little-endian fields ======================================= 82 83func nxzip_w16(o: *u8, p: i64, v: i64) -> i64 { 84 o[p] = (v & 255) as u8 85 o[p+1] = ((v >> 8) & 255) as u8 86 return 2 87} 88 89func nxzip_w32(o: *u8, p: i64, v: i64) -> i64 { 90 o[p] = (v & 255) as u8 91 o[p+1] = ((v >> 8) & 255) as u8 92 o[p+2] = ((v >> 16) & 255) as u8 93 o[p+3] = ((v >> 24) & 255) as u8 94 return 4 95} 96 97func nxzip_r16(b: *u8, p: i64) -> i64 { 98 return ((b[p] as i64) & 255) | (((b[p+1] as i64) & 255) << 8) 99} 100 101func nxzip_r32(b: *u8, p: i64) -> i64 { 102 return ((b[p] as i64) & 255) | (((b[p+1] as i64) & 255) << 8) 103 | (((b[p+2] as i64) & 255) << 16) | (((b[p+3] as i64) & 255) << 24) 104} 105 106func nxzip_strlen(s: *u8) -> i64 { 107 var i: i64 = 0 108 while s[i] != (0 as u8) { i = i + 1 } 109 return i 110} 111 112// ===== add one stored entry ======================================= 113// 114// Writes the local header + data into `out`, and the matching central 115// directory record into `cd`. st carries the running offsets and count. 116// Returns 1, or 0 if either buffer cannot hold the entry -- never a partial 117// entry, which would checksum fine and desync the central directory. 118 119func nxzip_add_stored(out: *u8, cap: i64, cd: *u8, cdcap: i64, st: *i64, 120 name: *u8, data: *u8, len: i64) -> i64 { 121 if len < 0 { return 0 } 122 let nlen: i64 = nxzip_strlen(name) 123 if nlen <= 0 { return 0 } 124 if nlen > NX_MAGIC_65535 { return 0 } 125 let off: i64 = st[NX_ZIP_ST_OFF] 126 let cdoff: i64 = st[NX_ZIP_ST_CDOFF] 127 if off + NX_ZIP_LOCAL_LEN + nlen + len > cap { return 0 } 128 if cdoff + NX_ZIP_CD_LEN + nlen > cdcap { return 0 } 129 130 let crc: i64 = nxzip_crc32(data, len) 131 132 nxzip_w32(out, off, NX_ZIP_SIG_LOCAL) 133 nxzip_w16(out, off + 4, NX_ZIP_VERSION) 134 nxzip_w16(out, off + 6, 0) 135 nxzip_w16(out, off + 8, NX_ZIP_METHOD_STORE) 136 nxzip_w16(out, off + 10, 0) 137 nxzip_w16(out, off + 12, 0) 138 nxzip_w32(out, off + 14, crc) 139 nxzip_w32(out, off + 18, len) 140 nxzip_w32(out, off + 22, len) 141 nxzip_w16(out, off + 26, nlen) 142 nxzip_w16(out, off + 28, 0) 143 var i: i64 = 0 144 while i < nlen { out[off + NX_ZIP_LOCAL_LEN + i] = name[i]; i = i + 1 } 145 let dstart: i64 = off + NX_ZIP_LOCAL_LEN + nlen 146 i = 0 147 while i < len { out[dstart + i] = data[i]; i = i + 1 } 148 149 nxzip_w32(cd, cdoff, NX_ZIP_SIG_CD) 150 nxzip_w16(cd, cdoff + 4, NX_ZIP_VERSION) 151 nxzip_w16(cd, cdoff + 6, NX_ZIP_VERSION) 152 nxzip_w16(cd, cdoff + 8, 0) 153 nxzip_w16(cd, cdoff + 10, NX_ZIP_METHOD_STORE) 154 nxzip_w16(cd, cdoff + 12, 0) 155 nxzip_w16(cd, cdoff + 14, 0) 156 nxzip_w32(cd, cdoff + 16, crc) 157 nxzip_w32(cd, cdoff + 20, len) 158 nxzip_w32(cd, cdoff + 24, len) 159 nxzip_w16(cd, cdoff + 28, nlen) 160 nxzip_w16(cd, cdoff + 30, 0) 161 nxzip_w16(cd, cdoff + 32, 0) 162 nxzip_w16(cd, cdoff + 34, 0) 163 nxzip_w16(cd, cdoff + 36, 0) 164 nxzip_w32(cd, cdoff + 38, 0) 165 // the offset readers actually trust 166 nxzip_w32(cd, cdoff + 42, off) 167 i = 0 168 while i < nlen { cd[cdoff + NX_ZIP_CD_LEN + i] = name[i]; i = i + 1 } 169 170 st[NX_ZIP_ST_OFF] = dstart + len 171 st[NX_ZIP_ST_CDOFF] = cdoff + NX_ZIP_CD_LEN + nlen 172 st[NX_ZIP_ST_COUNT] = st[NX_ZIP_ST_COUNT] + 1 173 return 1 174} 175 176// ===== finalise =================================================== 177// 178// Appends the accumulated central directory then the end-of-central-directory 179// record. Returns the total archive length, or 0. 180 181func nxzip_finalize(out: *u8, cap: i64, cd: *u8, st: *i64) -> i64 { 182 let off: i64 = st[NX_ZIP_ST_OFF] 183 let cdlen: i64 = st[NX_ZIP_ST_CDOFF] 184 let count: i64 = st[NX_ZIP_ST_COUNT] 185 if off + cdlen + NX_ZIP_EOCD_LEN > cap { return 0 } 186 187 var i: i64 = 0 188 while i < cdlen { out[off + i] = cd[i]; i = i + 1 } 189 let e: i64 = off + cdlen 190 nxzip_w32(out, e, NX_ZIP_SIG_EOCD) 191 nxzip_w16(out, e + 4, 0) 192 nxzip_w16(out, e + 6, 0) 193 nxzip_w16(out, e + 8, count) 194 nxzip_w16(out, e + 10, count) 195 nxzip_w32(out, e + 12, cdlen) 196 nxzip_w32(out, e + 16, off) 197 nxzip_w16(out, e + 20, 0) 198 return e + NX_ZIP_EOCD_LEN 199} 200 201// ===== reader ===================================================== 202// 203// Scans backwards for the EOCD signature, which is how every real unzip finds 204// it (a trailing comment may follow the record). 205 206func nxzip_find_eocd(data: *u8, n: i64) -> i64 { 207 if n < NX_ZIP_EOCD_LEN { return 0 - 1 } 208 var p: i64 = n - NX_ZIP_EOCD_LEN 209 while p >= 0 { 210 if nxzip_r32(data, p) == NX_ZIP_SIG_EOCD { return p } 211 p = p - 1 212 } 213 return 0 - 1 214} 215 216func nxzip_count(data: *u8, n: i64) -> i64 { 217 let e: i64 = nxzip_find_eocd(data, n) 218 if e < 0 { return 0 - 1 } 219 return nxzip_r16(data, e + 10) 220} 221 222func nxzip_cd_start(data: *u8, n: i64) -> i64 { 223 let e: i64 = nxzip_find_eocd(data, n) 224 if e < 0 { return 0 - 1 } 225 return nxzip_r32(data, e + 16) 226} 227 228// Read one central-directory record at cdp. Returns 1 and fills fld, or 0. 229func nxzip_read_cd(data: *u8, n: i64, cdp: i64, fld: *i64) -> i64 { 230 if cdp < 0 { return 0 } 231 if cdp + NX_ZIP_CD_LEN > n { return 0 } 232 if nxzip_r32(data, cdp) != NX_ZIP_SIG_CD { return 0 } 233 let nlen: i64 = nxzip_r16(data, cdp + 28) 234 let elen: i64 = nxzip_r16(data, cdp + 30) 235 let clen: i64 = nxzip_r16(data, cdp + 32) 236 if cdp + NX_ZIP_CD_LEN + nlen + elen + clen > n { return 0 } 237 fld[NX_ZIP_FLD_METHOD] = nxzip_r16(data, cdp + 10) 238 fld[NX_ZIP_FLD_CRC] = nxzip_r32(data, cdp + 16) 239 fld[NX_ZIP_FLD_CSIZE] = nxzip_r32(data, cdp + 20) 240 fld[NX_ZIP_FLD_USIZE] = nxzip_r32(data, cdp + 24) 241 fld[NX_ZIP_FLD_NAMELEN] = nlen 242 fld[NX_ZIP_FLD_NAMEOFF] = cdp + NX_ZIP_CD_LEN 243 fld[NX_ZIP_FLD_LOCALOFF] = nxzip_r32(data, cdp + 42) 244 fld[NX_ZIP_FLD_NEXTCD] = cdp + NX_ZIP_CD_LEN + nlen + elen + clen 245 return 1 246} 247 248// Resolve an entry's data offset from its LOCAL header and verify the CRC. 249// Returns the data offset, or -1 if the local header is wrong or the stored 250// bytes do not match the recorded checksum. 251func nxzip_entry_data(data: *u8, n: i64, fld: *i64) -> i64 { 252 let lo: i64 = fld[NX_ZIP_FLD_LOCALOFF] 253 if lo < 0 { return 0 - 1 } 254 if lo + NX_ZIP_LOCAL_LEN > n { return 0 - 1 } 255 if nxzip_r32(data, lo) != NX_ZIP_SIG_LOCAL { return 0 - 1 } 256 if nxzip_r16(data, lo + 8) != NX_ZIP_METHOD_STORE { return 0 - 1 } 257 let nlen: i64 = nxzip_r16(data, lo + 26) 258 let elen: i64 = nxzip_r16(data, lo + 28) 259 let doff: i64 = lo + NX_ZIP_LOCAL_LEN + nlen + elen 260 let sz: i64 = fld[NX_ZIP_FLD_CSIZE] 261 if doff + sz > n { return 0 - 1 } 262 if nxzip_crc32(data + doff, sz) != fld[NX_ZIP_FLD_CRC] { return 0 - 1 } 263 return doff 264}