mirror of
https://github.com/codedread/bitjs
synced 2025-10-05 02:19:24 +02:00
For issue #48, add Gunzipper that relies on DecompressionStream('gzip').
This commit is contained in:
parent
d01610ac9c
commit
813b154e8c
4 changed files with 179 additions and 1 deletions
125
archive/gunzip.js
Normal file
125
archive/gunzip.js
Normal file
|
@ -0,0 +1,125 @@
|
|||
/**
|
||||
* gunzip.js
|
||||
*
|
||||
* Licensed under the MIT License
|
||||
*
|
||||
* Copyright(c) 2024 Google Inc.
|
||||
*
|
||||
* Reference Documentation:
|
||||
*
|
||||
* https://www.ietf.org/rfc/rfc1952.txt
|
||||
*/
|
||||
|
||||
import { BitStream } from '../io/bitstream.js';
|
||||
import { ByteStream } from '../io/bytestream.js';
|
||||
|
||||
/** @type {MessagePort} */
|
||||
let hostPort;
|
||||
|
||||
/** @type {ByteStream} */
|
||||
let bstream = null;
|
||||
// undefined unless a FNAME block is present.
|
||||
let filename;
|
||||
|
||||
const err = str => hostPort.postMessage({ type: 'error', msg: str });
|
||||
|
||||
async function gunzip() {
|
||||
const sig = bstream.readBytes(2);
|
||||
if (sig[0] !== 0x1F || sig[1] !== 0x8B) {
|
||||
const errMsg = `First two bytes not 0x1F, 0x8B: ${sig[0].toString(16)} ${sig[1].toString(16)}`;
|
||||
err(errMsg);
|
||||
return;
|
||||
}
|
||||
const compressionMethod = bstream.readNumber(1);
|
||||
if (compressionMethod !== 8) {
|
||||
const errMsg = `Compression method ${compressionMethod} not supported`;
|
||||
err(errMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the GZIP header to see if we can find a filename (FNAME block).
|
||||
const flags = new BitStream(bstream.readBytes(1).buffer);
|
||||
flags.skip(1); // skip FTEXT bit
|
||||
const fhcrc = flags.readBits(1);
|
||||
const fextra = flags.readBits(1);
|
||||
const fname = flags.readBits(1);
|
||||
const fcomment = flags.readBits(1);
|
||||
|
||||
bstream.skip(4); // MTIME
|
||||
bstream.skip(1); // XFL
|
||||
bstream.skip(1); // OS
|
||||
|
||||
if (fextra) {
|
||||
const xlen = bstream.readNumber(2);
|
||||
bstream.skip(xlen);
|
||||
}
|
||||
|
||||
if (fname) {
|
||||
// Find the null-terminator byte.
|
||||
let numBytes = 0;
|
||||
const findNull = bstream.tee();
|
||||
while (findNull.readNumber(1) !== 0) numBytes++;
|
||||
filename = bstream.readString(numBytes);
|
||||
}
|
||||
|
||||
if (fcomment) {
|
||||
// Find the null-terminator byte.
|
||||
let numBytes = 0;
|
||||
const findNull = bstream.tee();
|
||||
while (findNull.readNumber(1) !== 0) numBytes++;
|
||||
bstream.skip(numBytes); // COMMENT
|
||||
}
|
||||
|
||||
if (fhcrc) {
|
||||
bstream.readNumber(2); // CRC16
|
||||
}
|
||||
|
||||
// Now try to use native implementation of INFLATE, if supported by the runtime.
|
||||
const blob = new Blob([bstream.bytes.buffer]);
|
||||
const decompressedStream = blob.stream().pipeThrough(new DecompressionStream('gzip'));
|
||||
const fileData = new Uint8Array(await new Response(decompressedStream).arrayBuffer());
|
||||
const unarchivedFile = { filename, fileData };
|
||||
hostPort.postMessage({ type: 'extract', unarchivedFile }, [fileData.buffer]);
|
||||
|
||||
// TODO: Supported chunked decompression?
|
||||
// TODO: Fall through to non-native implementation via inflate() ?
|
||||
|
||||
hostPort.postMessage({ type: 'finish', metadata: {} });
|
||||
}
|
||||
|
||||
// event.data.file has the first ArrayBuffer.
|
||||
const onmessage = async function (event) {
|
||||
const bytes = event.data.file;
|
||||
|
||||
if (!bstream) {
|
||||
bstream = new ByteStream(bytes);
|
||||
bstream.setLittleEndian(true);
|
||||
} else {
|
||||
throw `Gunzipper does not calling update() with more bytes. Send the whole file with start().`
|
||||
}
|
||||
|
||||
await gunzip();
|
||||
};
|
||||
|
||||
/**
|
||||
* Connect the host to the gunzip implementation with the given MessagePort.
|
||||
* @param {MessagePort} port
|
||||
*/
|
||||
export function connect(port) {
|
||||
if (hostPort) {
|
||||
throw `connect(): hostPort already connected in gunzip.js`;
|
||||
}
|
||||
|
||||
hostPort = port;
|
||||
port.onmessage = onmessage;
|
||||
}
|
||||
|
||||
export function disconnect() {
|
||||
if (!hostPort) {
|
||||
throw `disconnect(): hostPort was not connected in gunzip.js`;
|
||||
}
|
||||
|
||||
hostPort = null;
|
||||
bstream = null;
|
||||
filename = undefined;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue