mirror of
https://github.com/swc-project/swc.git
synced 2024-12-23 05:32:09 +03:00
306 lines
6.7 KiB
TypeScript
306 lines
6.7 KiB
TypeScript
// Loaded from https://deno.land/x/compress@v0.3.8/gzip/gzip.ts
|
|
|
|
|
|
import { crc32 } from "../deps.ts";
|
|
/** very fast */
|
|
import { deflateRaw, inflateRaw } from "../zlib/mod.ts";
|
|
/** slow */
|
|
// import { deflateRaw, inflateRaw } from "../deflate/mod.ts";
|
|
|
|
// magic numbers marking this file as GZIP
|
|
const ID1 = 0x1F;
|
|
const ID2 = 0x8B;
|
|
|
|
const compressionMethods = {
|
|
"deflate": 8,
|
|
};
|
|
const possibleFlags = {
|
|
"FTEXT": 0x01,
|
|
"FHCRC": 0x02,
|
|
"FEXTRA": 0x04,
|
|
"FNAME": 0x08,
|
|
"FCOMMENT": 0x10,
|
|
};
|
|
// const osMap = {
|
|
// "fat": 0, // FAT file system (DOS, OS/2, NT) + PKZIPW 2.50 VFAT, NTFS
|
|
// "amiga": 1, // Amiga
|
|
// "vmz": 2, // VMS (VAX or Alpha AXP)
|
|
// "unix": 3, // Unix
|
|
// "vm/cms": 4, // VM/CMS
|
|
// "atari": 5, // Atari
|
|
// "hpfs": 6, // HPFS file system (OS/2, NT 3.x)
|
|
// "macintosh": 7, // Macintosh
|
|
// "z-system": 8, // Z-System
|
|
// "cplm": 9, // CP/M
|
|
// "tops-20": 10, // TOPS-20
|
|
// "ntfs": 11, // NTFS file system (NT)
|
|
// "qdos": 12, // SMS/QDOS
|
|
// "acorn": 13, // Acorn RISC OS
|
|
// "vfat": 14, // VFAT file system (Win95, NT)
|
|
// "vms": 15, // MVS (code also taken for PRIMOS)
|
|
// "beos": 16, // BeOS (BeBox or PowerMac)
|
|
// "tandem": 17, // Tandem/NSK
|
|
// "theos": 18, // THEOS
|
|
// };
|
|
const os = {
|
|
"darwin": 3,
|
|
"linux": 3,
|
|
"windows": 0,
|
|
};
|
|
|
|
const osCode = os[Deno.build.os] ?? 255;
|
|
export const DEFAULT_LEVEL = 6;
|
|
|
|
function putByte(n: number, arr: number[]) {
|
|
arr.push(n & 0xFF);
|
|
}
|
|
|
|
// LSB first
|
|
function putShort(n: number, arr: number[]) {
|
|
arr.push(n & 0xFF);
|
|
arr.push(n >>> 8);
|
|
}
|
|
|
|
// LSB first
|
|
export function putLong(n: number, arr: number[]) {
|
|
putShort(n & 0xffff, arr);
|
|
putShort(n >>> 16, arr);
|
|
}
|
|
|
|
function putString(s: string, arr: number[]) {
|
|
for (let i = 0, len = s.length; i < len; i += 1) {
|
|
putByte(s.charCodeAt(i), arr);
|
|
}
|
|
}
|
|
|
|
function readByte(arr: number[]): number {
|
|
return arr.shift()!;
|
|
}
|
|
|
|
function readShort(arr: number[]) {
|
|
return arr.shift()! | (arr.shift()! << 8);
|
|
}
|
|
|
|
function readLong(arr: number[]) {
|
|
let n1 = readShort(arr);
|
|
let n2 = readShort(arr);
|
|
|
|
// JavaScript can't handle bits in the position 32
|
|
// we'll emulate this by removing the left-most bit (if it exists)
|
|
// and add it back in via multiplication, which does work
|
|
if (n2 > 32768) {
|
|
n2 -= 32768;
|
|
|
|
return ((n2 << 16) | n1) + 32768 * Math.pow(2, 16);
|
|
}
|
|
|
|
return (n2 << 16) | n1;
|
|
}
|
|
|
|
function readString(arr: number[]) {
|
|
const charArr = [];
|
|
|
|
// turn all bytes into chars until the terminating null
|
|
while (arr[0] !== 0) {
|
|
charArr.push(String.fromCharCode(arr.shift()!));
|
|
}
|
|
|
|
// throw away terminating null
|
|
arr.shift();
|
|
|
|
// join all characters into a cohesive string
|
|
return charArr.join("");
|
|
}
|
|
|
|
function readBytes(arr: number[], n: number) {
|
|
const ret = [];
|
|
for (let i = 0; i < n; i += 1) {
|
|
ret.push(arr.shift());
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
interface Options {
|
|
level?: number;
|
|
timestamp?: number;
|
|
name?: string;
|
|
}
|
|
|
|
export function getHeader(
|
|
options: Options = {},
|
|
): Uint8Array {
|
|
let flags: number = 0;
|
|
let level: number = options.level ?? DEFAULT_LEVEL;
|
|
const out: number[] = [];
|
|
|
|
putByte(ID1, out);
|
|
putByte(ID2, out);
|
|
|
|
putByte(compressionMethods["deflate"], out);
|
|
|
|
if (options.name) {
|
|
flags |= possibleFlags["FNAME"];
|
|
}
|
|
|
|
putByte(flags, out);
|
|
putLong(options.timestamp ?? Math.floor(Date.now() / 1000), out);
|
|
|
|
// put deflate args (extra flags)
|
|
if (level === 1) {
|
|
// fastest algorithm
|
|
putByte(4, out);
|
|
} else if (level === 9) {
|
|
// maximum compression (fastest algorithm)
|
|
putByte(2, out);
|
|
} else {
|
|
putByte(0, out);
|
|
}
|
|
|
|
// OS identifier
|
|
putByte(osCode, out);
|
|
|
|
if (options.name) {
|
|
// ignore the directory part
|
|
putString(options.name.substring(options.name.lastIndexOf("/") + 1), out);
|
|
|
|
// terminating null
|
|
putByte(0, out);
|
|
}
|
|
|
|
return new Uint8Array(out);
|
|
}
|
|
|
|
export function gzip(
|
|
bytes: Uint8Array,
|
|
options: Options = {},
|
|
): Uint8Array {
|
|
let flags: number = 0;
|
|
let level: number = options.level ?? DEFAULT_LEVEL;
|
|
const out: number[] = [];
|
|
|
|
putByte(ID1, out);
|
|
putByte(ID2, out);
|
|
|
|
putByte(compressionMethods["deflate"], out);
|
|
|
|
if (options.name) {
|
|
flags |= possibleFlags["FNAME"];
|
|
}
|
|
|
|
putByte(flags, out);
|
|
putLong(options.timestamp ?? Math.floor(Date.now() / 1000), out);
|
|
|
|
// put deflate args (extra flags)
|
|
if (level === 1) {
|
|
// fastest algorithm
|
|
putByte(4, out);
|
|
} else if (level === 9) {
|
|
// maximum compression (fastest algorithm)
|
|
putByte(2, out);
|
|
} else {
|
|
putByte(0, out);
|
|
}
|
|
|
|
// OS identifier
|
|
putByte(osCode, out);
|
|
|
|
if (options.name) {
|
|
// ignore the directory part
|
|
putString(options.name.substring(options.name.lastIndexOf("/") + 1), out);
|
|
|
|
// terminating null
|
|
putByte(0, out);
|
|
}
|
|
|
|
deflateRaw(bytes).forEach(function (byte) {
|
|
putByte(byte, out);
|
|
});
|
|
// import { deflateRaw, inflateRaw } from "../deflate/mod.ts";
|
|
// deflateRaw(bytes, level).forEach(function (byte) {
|
|
// putByte(byte, out);
|
|
// });
|
|
|
|
putLong(parseInt(crc32(bytes), 16), out);
|
|
putLong(bytes.length, out);
|
|
|
|
return new Uint8Array(out);
|
|
}
|
|
|
|
export function gunzip(bytes: Uint8Array): Uint8Array {
|
|
const arr = Array.from(bytes);
|
|
|
|
checkHeader(arr);
|
|
|
|
// give deflate everything but the last 8 bytes
|
|
// the last 8 bytes are for the CRC32 checksum and filesize
|
|
let res: Uint8Array = inflateRaw(
|
|
new Uint8Array(arr.splice(0, arr.length - 8)),
|
|
);
|
|
|
|
// if (flags & possibleFlags["FTEXT"]) {
|
|
// res = Array.prototype.map.call(res, function (byte) {
|
|
// return String.fromCharCode(byte);
|
|
// }).join("");
|
|
// }
|
|
|
|
let crc: number = readLong(arr) >>> 0;
|
|
if (crc !== parseInt(crc32(res), 16)) {
|
|
throw "Checksum does not match";
|
|
}
|
|
|
|
let size: number = readLong(arr);
|
|
if (size !== res.length) {
|
|
throw "Size of decompressed file not correct";
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
export function checkHeader(arr: number[]) {
|
|
// check the first two bytes for the magic numbers
|
|
if (readByte(arr) !== ID1 || readByte(arr) !== ID2) {
|
|
throw "Not a GZIP file";
|
|
}
|
|
if (readByte(arr) !== 8) {
|
|
throw "Unsupported compression method";
|
|
}
|
|
|
|
let flags: number = readByte(arr);
|
|
readLong(arr); // mtime
|
|
readByte(arr); // xFlags
|
|
readByte(arr); // os, throw away
|
|
|
|
// just throw away the bytes for now
|
|
if (flags & possibleFlags["FEXTRA"]) {
|
|
let t: number = readShort(arr);
|
|
readBytes(arr, t);
|
|
}
|
|
|
|
// just throw away for now
|
|
if (flags & possibleFlags["FNAME"]) {
|
|
readString(arr);
|
|
}
|
|
|
|
// just throw away for now
|
|
if (flags & possibleFlags["FCOMMENT"]) {
|
|
readString(arr);
|
|
}
|
|
|
|
// just throw away for now
|
|
if (flags & possibleFlags["FHCRC"]) {
|
|
readShort(arr);
|
|
}
|
|
}
|
|
|
|
export function checkTail(arr: number[]) {
|
|
const tail = arr.splice(arr.length - 8);
|
|
|
|
let crc32: number = readLong(tail) >>> 0;
|
|
let size: number = readLong(tail);
|
|
|
|
return {
|
|
crc32,
|
|
size,
|
|
};
|
|
}
|