zip.ts 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. /**
  2. * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. *
  6. * ported from https://github.com/photopea/UZIP.js/blob/master/UZIP.js
  7. * MIT License, Copyright (c) 2018 Photopea
  8. *
  9. * - added `ungzip`
  10. */
  11. import { writeUint, writeUshort, sizeUTF8, writeUTF8, readUshort, readUint, readUTF8, toInt32 } from './bin';
  12. import { crc, adler } from './checksum';
  13. import { _inflate } from './inflate';
  14. import { _deflateRaw } from './deflate';
  15. import { RuntimeContext } from '../../mol-task';
  16. export async function unzip(runtime: RuntimeContext, buf: ArrayBuffer, onlyNames = false) {
  17. const out: { [k: string]: Uint8Array | { size: number, csize: number } } = Object.create(null);
  18. const data = new Uint8Array(buf);
  19. let eocd = data.length-4;
  20. while(readUint(data, eocd) !== 0x06054b50) eocd--;
  21. let o = eocd;
  22. o += 4; // sign = 0x06054b50
  23. o += 4; // disks = 0;
  24. const cnu = readUshort(data, o);
  25. o += 2;
  26. // const cnt = readUshort(data, o);
  27. o += 2;
  28. // const csize = readUint(data, o);
  29. o+=4;
  30. const coffs = readUint(data, o); o+=4;
  31. o = coffs;
  32. for(let i = 0; i<cnu; i++) {
  33. // const sign = readUint(data, o);
  34. o += 4;
  35. o += 4; // versions;
  36. o += 4; // flag + compr
  37. o += 4; // time
  38. // const crc32 = readUint(data, o);
  39. o+=4;
  40. const csize = readUint(data, o);
  41. o+=4;
  42. const usize = readUint(data, o);
  43. o+=4;
  44. const nl = readUshort(data, o)
  45. const el = readUshort(data, o+2)
  46. const cl = readUshort(data, o+4);
  47. o += 6; // name, extra, comment
  48. o += 8; // disk, attribs
  49. const roff = readUint(data, o); o+=4;
  50. o += nl + el + cl;
  51. await _readLocal(runtime, data, roff, out, csize, usize, onlyNames);
  52. }
  53. // console.log(out);
  54. return out;
  55. }
  56. async function _readLocal(runtime: RuntimeContext, data: Uint8Array, o: number, out: { [k: string]: Uint8Array | { size: number, csize: number } }, csize: number, usize: number, onlyNames: boolean) {
  57. // const sign = readUint(data, o);
  58. o+=4;
  59. // const ver = readUshort(data, o);
  60. o+=2;
  61. // const gpflg = readUshort(data, o);
  62. o+=2;
  63. // if((gpflg&8)!=0) throw "unknown sizes";
  64. const cmpr = readUshort(data, o);
  65. o+=2;
  66. // const time = readUint(data, o);
  67. o+=4;
  68. // const crc32 = readUint(data, o);
  69. o+=4;
  70. // var csize = rUi(data, o); o+=4;
  71. // var usize = rUi(data, o); o+=4;
  72. o+=8;
  73. const nlen = readUshort(data, o);
  74. o+=2;
  75. const elen = readUshort(data, o);
  76. o+=2;
  77. const name = readUTF8(data, o, nlen);
  78. o += nlen; // console.log(name);
  79. o += elen;
  80. if(onlyNames) {
  81. out[name] = { size: usize, csize };
  82. return;
  83. }
  84. const file = new Uint8Array(data.buffer, o);
  85. if(cmpr === 0) {
  86. out[name] = new Uint8Array(file.buffer.slice(o, o+csize));
  87. } else if(cmpr === 8) {
  88. const buf = new Uint8Array(usize);
  89. await inflateRaw(runtime, file, buf);
  90. out[name] = buf;
  91. } else {
  92. throw `unknown compression method: ${cmpr}`;
  93. }
  94. }
  95. export async function inflateRaw(runtime: RuntimeContext, file: Uint8Array, buf?: Uint8Array) {
  96. return _inflate(runtime, file, buf);
  97. }
  98. export function inflate(runtime: RuntimeContext, file: Uint8Array, buf?: Uint8Array) {
  99. // const CMF = file[0]
  100. // const FLG = file[1]
  101. // const CM = (CMF&15)
  102. // const CINFO = (CMF>>>4);
  103. // console.log(CM, CINFO,CMF,FLG);
  104. return inflateRaw(runtime, new Uint8Array(file.buffer, file.byteOffset+2, file.length-6), buf);
  105. }
  106. // https://tools.ietf.org/html/rfc1952
  107. export async function ungzip(runtime: RuntimeContext, file: Uint8Array, buf?: Uint8Array) {
  108. // const id1 = file[0]
  109. // const id2 = file[1]
  110. // const cm = file[2]
  111. const flg = file[3]
  112. // const mtime = readUint(file, 4)
  113. // const xfl = file[8]
  114. // const os = file[9]
  115. let o = 10
  116. if (flg & 4) { // FEXTRA
  117. const xlen = readUshort(file, o)
  118. // console.log('FEXTRA', xlen)
  119. o += xlen
  120. }
  121. if (flg & 8) { // FNAME
  122. let zero = o
  123. while(file[zero] !== 0) ++zero
  124. // const name = readUTF8(file, o, zero - o)
  125. // console.log('FNAME', name, zero - o)
  126. o = zero + 1
  127. }
  128. if (flg & 16) { // FCOMMENT
  129. let zero = o
  130. while(file[zero] !== 0) ++zero
  131. // const comment = readUTF8(file, o, zero - o)
  132. // console.log('FCOMMENT', comment)
  133. o = zero + 1
  134. }
  135. if (flg & 1) { // FHCRC
  136. // const hcrc = readUshort(file, o)
  137. // console.log('FHCRC', hcrc)
  138. o += 2
  139. }
  140. const crc32 = toInt32(readUint(file, file.length - 8))
  141. const isize = readUint(file, file.length - 4)
  142. if (buf === undefined) buf = new Uint8Array(isize)
  143. const blocks = new Uint8Array(file.buffer, file.byteOffset + o, file.length - o - 8)
  144. const inflated = await inflateRaw(runtime, blocks, buf);
  145. const crcValue = crc(inflated, 0, inflated.length)
  146. if (crc32 !== crcValue) {
  147. console.error("ungzip: checksums don't match")
  148. }
  149. return inflated
  150. }
  151. export function deflate(data: Uint8Array, opts?: { level: number }/* , buf, off*/) {
  152. if(opts === undefined) opts={ level: 6 };
  153. let off=0
  154. const buf = new Uint8Array(50 + Math.floor(data.length * 1.1));
  155. buf[off]=120; buf[off+1]=156; off+=2;
  156. off = _deflateRaw(data, buf, off, opts.level);
  157. const crcValue = adler(data, 0, data.length);
  158. buf[off+0] = ((crcValue>>>24)&255);
  159. buf[off+1] = ((crcValue>>>16)&255);
  160. buf[off+2] = ((crcValue>>> 8)&255);
  161. buf[off+3] = ((crcValue>>> 0)&255);
  162. return new Uint8Array(buf.buffer, 0, off+4);
  163. }
  164. function deflateRaw(data: Uint8Array, opts?: { level: number }) {
  165. if(opts === undefined) opts = { level: 6 };
  166. const buf = new Uint8Array(50 + Math.floor(data.length * 1.1));
  167. const off = _deflateRaw(data, buf, 0, opts.level);
  168. return new Uint8Array(buf.buffer, 0, off);
  169. }
  170. export function zip(obj: { [k: string]: Uint8Array }, noCmpr = false) {
  171. let tot = 0;
  172. const zpd: { [k: string]: { cpr: boolean, usize: number, crc: number, file: Uint8Array } } = {};
  173. for(const p in obj) {
  174. const cpr = !_noNeed(p) && !noCmpr, buf = obj[p]
  175. const crcValue = crc(buf, 0, buf.length);
  176. zpd[p] = {
  177. cpr,
  178. usize: buf.length,
  179. crc: crcValue,
  180. file: (cpr ? deflateRaw(buf) : buf)
  181. };
  182. }
  183. for(const p in zpd) tot += zpd[p].file.length + 30 + 46 + 2 * sizeUTF8(p);
  184. tot += 22;
  185. const data = new Uint8Array(tot)
  186. let o = 0;
  187. const fof = []
  188. for(const p in zpd) {
  189. const file = zpd[p]; fof.push(o);
  190. o = _writeHeader(data, o, p, file, 0);
  191. }
  192. let i=0, ioff = o;
  193. for(const p in zpd) {
  194. const file = zpd[p];
  195. fof.push(o);
  196. o = _writeHeader(data, o, p, file, 1, fof[i++]);
  197. }
  198. const csize = o-ioff;
  199. writeUint(data, o, 0x06054b50); o+=4;
  200. o += 4; // disks
  201. writeUshort(data, o, i); o += 2;
  202. writeUshort(data, o, i); o += 2; // number of c d records
  203. writeUint(data, o, csize); o += 4;
  204. writeUint(data, o, ioff ); o += 4;
  205. o += 2;
  206. return data.buffer;
  207. }
  208. // no need to compress .PNG, .ZIP, .JPEG ....
  209. function _noNeed(fn: string) {
  210. const ext = fn.split('.').pop()!.toLowerCase();
  211. return 'png,jpg,jpeg,zip'.indexOf(ext) !== -1;
  212. }
  213. function _writeHeader(data: Uint8Array, o: number, p: string, obj: { cpr: boolean, usize: number, crc: number, file: Uint8Array }, t: number, roff = 0) {
  214. const file = obj.file;
  215. writeUint(data, o, t === 0 ? 0x04034b50 : 0x02014b50); o+=4; // sign
  216. if(t === 1) o+=2; // ver made by
  217. writeUshort(data, o, 20); o+=2; // ver
  218. writeUshort(data, o, 0); o+=2; // gflip
  219. writeUshort(data, o, obj.cpr?8:0); o+=2; // cmpr
  220. writeUint(data, o, 0); o+=4; // time
  221. writeUint(data, o, obj.crc); o+=4; // crc32
  222. writeUint(data, o, file.length); o+=4; // csize
  223. writeUint(data, o, obj.usize); o+=4; // usize
  224. writeUshort(data, o, sizeUTF8(p)); o+=2; // nlen
  225. writeUshort(data, o, 0); o+=2; // elen
  226. if(t === 1) {
  227. o += 2; // comment length
  228. o += 2; // disk number
  229. o += 6; // attributes
  230. writeUint(data, o, roff); o+=4; // usize
  231. }
  232. const nlen = writeUTF8(data, o, p); o+= nlen;
  233. if(t === 0) {
  234. data.set(file, o);
  235. o += file.length;
  236. }
  237. return o;
  238. }