1
0
Fork 0
mirror of https://github.com/codedread/bitjs synced 2025-10-03 17:49:16 +02:00

Fix issue #38: Use runtime's native DecompressionStream to inflate zip streams.

This commit is contained in:
Jeff Schiller 2024-01-25 19:58:18 -08:00
parent 5bf583c617
commit 4ca68bd336
4 changed files with 48 additions and 15 deletions

View file

@ -7,9 +7,16 @@ All notable changes to this project will be documented in this file.
### Added ### Added
- archive: Support DEFLATE in Zipper where JS implementations support it in CompressionStream. - archive: Support DEFLATE in Zipper where JS implementations support it in CompressionStream.
[Issue #40](https://github.com/codedread/bitjs/issues/40)
- archive: Support DEFLATE in Unzipper where JS implementations support it in DecompressionStream.
[Issue #38](https://github.com/codedread/bitjs/issues/38)
- file: Added detection of GZIP files. - file: Added detection of GZIP files.
- io: Added a skip() method to BitStream to match ByteStream. - io: Added a skip() method to BitStream to match ByteStream.
### Fixed
- Fixed a benign JS error in the Web Worker wrapper
## [1.2.1] - 2024-01-19 ## [1.2.1] - 2024-01-19
### Added ### Added

View file

@ -8,15 +8,13 @@ A set of dependency-free JavaScript modules to handle binary data in JS (using
[Typed Arrays](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray)). [Typed Arrays](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray)).
Includes: Includes:
* bitjs/archive: Unarchiving files (unzip, unrar, untar) in JavaScript, * bitjs/archive: Decompressing files (unzip, unrar, untar) in JavaScript, implemented as Web
implemented as Web Workers where supported, and allowing progressive Workers where supported, and allowing progressive unarchiving while streaming.
unarchiving while streaming. * bitjs/codecs: Get the codec info of media containers in a ISO RFC6381 MIME type string.
* bitjs/codecs: Get the codec info of media containers in a ISO RFC6381
MIME type string
* bitjs/file: Detect the type of file from its binary signature. * bitjs/file: Detect the type of file from its binary signature.
* bitjs/image: Parsing GIF, JPEG, PNG. Conversion of WebP to PNG or JPEG. * bitjs/image: Parsing GIF, JPEG, PNG. Conversion of WebP to PNG or JPEG.
* bitjs/io: Low-level classes for interpreting binary data (BitStream * bitjs/io: Low-level classes for interpreting binary data (BitStream, ByteStream). For example,
ByteStream). For example, reading or peeking at N bits at a time. reading or peeking at N bits at a time.
## Installation ## Installation

View file

@ -316,3 +316,22 @@ export function getUnarchiver(ab, options = {}) {
} }
return unarchiver; return unarchiver;
} }
// import * as fs from 'node:fs';
// async function main() {
// const nodeBuf = fs.readFileSync(`./action-1.cbz`);
// const ab = nodeBuf.buffer.slice(nodeBuf.byteOffset, nodeBuf.byteOffset + nodeBuf.length);
// const then = Date.now();
// const zipper = new Unzipper(ab, {debug: true});
// zipper.addEventListener('extract', evt => {
// const f = evt.unarchivedFile;
// fs.writeFileSync(f.filename, Buffer.from(f.fileData));
// });
// zipper.addEventListener('finish', evt => {
// console.dir(evt);
// console.log(`Took ${(Date.now() - then)}ms`);
// });
// await zipper.start();
// }
// main();

View file

@ -178,7 +178,7 @@ class ZipLocalFile {
} }
// determine what kind of compressed data we have and decompress // determine what kind of compressed data we have and decompress
unzip() { async unzip() {
if (!this.fileData) { if (!this.fileData) {
err('unzip() called on a file with out compressed file data'); err('unzip() called on a file with out compressed file data');
} }
@ -196,7 +196,7 @@ class ZipLocalFile {
if (logToConsole) { if (logToConsole) {
info(`ZIP v2.0, DEFLATE: ${this.filename} (${this.compressedSize} bytes)`); info(`ZIP v2.0, DEFLATE: ${this.filename} (${this.compressedSize} bytes)`);
} }
this.fileData = inflate(this.fileData, this.uncompressedSize); this.fileData = await inflate(this.fileData, this.uncompressedSize);
} }
else { else {
err(`UNSUPPORTED VERSION/FORMAT: ZIP v${this.version}, ` + err(`UNSUPPORTED VERSION/FORMAT: ZIP v${this.version}, ` +
@ -483,9 +483,18 @@ function inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer) {
* Compression method 8. Deflate: http://tools.ietf.org/html/rfc1951 * Compression method 8. Deflate: http://tools.ietf.org/html/rfc1951
* @param {Uint8Array} compressedData A Uint8Array of the compressed file data. * @param {Uint8Array} compressedData A Uint8Array of the compressed file data.
* @param {number} numDecompressedBytes * @param {number} numDecompressedBytes
* @returns {Uint8Array} The decompressed array. * @returns {Promise<Uint8Array>} The decompressed array.
*/ */
function inflate(compressedData, numDecompressedBytes) { async function inflate(compressedData, numDecompressedBytes) {
// Try to use native implementation of DEFLATE if it exists.
try {
const blob = new Blob([compressedData.buffer]);
const decompressedStream = blob.stream().pipeThrough(new DecompressionStream('deflate-raw'));
return new Uint8Array(await new Response(decompressedStream).arrayBuffer());
} catch (err) {
// Fall through to non-native implementation of DEFLATE.
}
// Bit stream representing the compressed data. // Bit stream representing the compressed data.
/** @type {BitStream} */ /** @type {BitStream} */
const bstream = new BitStream(compressedData.buffer, const bstream = new BitStream(compressedData.buffer,
@ -596,7 +605,7 @@ function inflate(compressedData, numDecompressedBytes) {
return buffer.data; return buffer.data;
} }
function archiveUnzip() { async function archiveUnzip() {
let bstream = bytestream.tee(); let bstream = bytestream.tee();
// loop until we don't see any more local files or we find a data descriptor. // loop until we don't see any more local files or we find a data descriptor.
@ -619,7 +628,7 @@ function archiveUnzip() {
currentBytesUnarchivedInFile = 0; currentBytesUnarchivedInFile = 0;
// Actually do the unzipping. // Actually do the unzipping.
oneLocalFile.unzip(); await oneLocalFile.unzip();
if (oneLocalFile.fileData != null) { if (oneLocalFile.fileData != null) {
hostPort.postMessage({ type: 'extract', unarchivedFile: oneLocalFile }, [oneLocalFile.fileData.buffer]); hostPort.postMessage({ type: 'extract', unarchivedFile: oneLocalFile }, [oneLocalFile.fileData.buffer]);
@ -724,7 +733,7 @@ function archiveUnzip() {
// event.data.file has the first ArrayBuffer. // event.data.file has the first ArrayBuffer.
// event.data.bytes has all subsequent ArrayBuffers. // event.data.bytes has all subsequent ArrayBuffers.
const onmessage = function (event) { const onmessage = async function (event) {
const bytes = event.data.file || event.data.bytes; const bytes = event.data.file || event.data.bytes;
logToConsole = !!event.data.logToConsole; logToConsole = !!event.data.logToConsole;
@ -755,7 +764,7 @@ const onmessage = function (event) {
if (unarchiveState === UnarchiveState.UNARCHIVING || if (unarchiveState === UnarchiveState.UNARCHIVING ||
unarchiveState === UnarchiveState.WAITING) { unarchiveState === UnarchiveState.WAITING) {
try { try {
archiveUnzip(); await archiveUnzip();
} catch (e) { } catch (e) {
if (typeof e === 'string' && e.startsWith('Error! Overflowed')) { if (typeof e === 'string' && e.startsWith('Error! Overflowed')) {
// Overrun the buffer. // Overrun the buffer.