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

Move some common type definitions into archive/common.js and some comments.

This commit is contained in:
Jeff Schiller 2024-01-20 16:12:55 -08:00
parent 4fc0c3da2b
commit 45fdedd663
11 changed files with 75 additions and 83 deletions

View file

@ -1,14 +1,19 @@
/** /**
* common.js * common.js
* *
* Provides common functionality for compressing and decompressing. * Provides common definitions or functionality needed by multiple modules.
* *
* Licensed under the MIT License * Licensed under the MIT License
* *
* Copyright(c) 2023 Google Inc. * Copyright(c) 2023 Google Inc.
*/ */
// Requires the following JavaScript features: MessageChannel, MessagePort, and dynamic imports. /**
* @typedef FileInfo An object that is sent to the implementation representing a file to compress.
* @property {string} fileName The name of the file. TODO: Includes the path?
* @property {number} lastModTime The number of ms since the Unix epoch (1970-01-01 at midnight).
* @property {Uint8Array} fileData The bytes of the file.
*/
/** /**
* @typedef Implementation * @typedef Implementation
@ -51,3 +56,23 @@ export async function getConnectedPort(implFilename) {
}); });
}); });
} }
// Zip-specific things.
export const LOCAL_FILE_HEADER_SIG = 0x04034b50;
export const CENTRAL_FILE_HEADER_SIG = 0x02014b50;
export const END_OF_CENTRAL_DIR_SIG = 0x06054b50;
export const CRC32_MAGIC_NUMBER = 0xedb88320;
export const ARCHIVE_EXTRA_DATA_SIG = 0x08064b50;
export const DIGITAL_SIGNATURE_SIG = 0x05054b50;
export const END_OF_CENTRAL_DIR_LOCATOR_SIG = 0x07064b50;
export const DATA_DESCRIPTOR_SIG = 0x08074b50;
/**
* @readonly
* @enum {number}
*/
export const ZipCompressionMethod = {
STORE: 0, // Default.
DEFLATE: 8, // As per http://tools.ietf.org/html/rfc1951.
};

View file

@ -8,7 +8,7 @@
* Copyright(c) 2023 Google Inc. * Copyright(c) 2023 Google Inc.
*/ */
import { getConnectedPort } from './common.js'; import { ZipCompressionMethod, getConnectedPort } from './common.js';
// NOTE: THIS IS A VERY HACKY WORK-IN-PROGRESS! THE API IS NOT FROZEN! USE AT YOUR OWN RISK! // NOTE: THIS IS A VERY HACKY WORK-IN-PROGRESS! THE API IS NOT FROZEN! USE AT YOUR OWN RISK!
@ -19,15 +19,6 @@ import { getConnectedPort } from './common.js';
* @property {Uint8Array} fileData The bytes of the file. * @property {Uint8Array} fileData The bytes of the file.
*/ */
/**
* @readonly
* @enum {number}
*/
export const ZipCompressionMethod = {
STORE: 0, // Default.
// DEFLATE: 8,
};
// export const DeflateCompressionMethod = { // export const DeflateCompressionMethod = {
// NO_COMPRESSION: 0, // NO_COMPRESSION: 0,
// COMPRESSION_FIXED_HUFFMAN: 1, // COMPRESSION_FIXED_HUFFMAN: 1,
@ -36,17 +27,15 @@ export const ZipCompressionMethod = {
/** /**
* Data elements are packed into bytes in order of increasing bit number within the byte, * Data elements are packed into bytes in order of increasing bit number within the byte,
i.e., starting with the least-significant bit of the byte. * i.e., starting with the least-significant bit of the byte.
* Data elements other than Huffman codes are packed starting with the least-significant bit of the * Data elements other than Huffman codes are packed starting with the least-significant bit of the
data element. * data element.
* Huffman codes are packed starting with the most-significant bit of the code. * Huffman codes are packed starting with the most-significant bit of the code.
*/ */
/** /**
* @typedef CompressorOptions * @typedef CompressorOptions
* @property {ZipCompressionMethod} zipCompressionMethod * @property {ZipCompressionMethod} zipCompressionMethod
* @property {DeflateCompressionMethod=} deflateCompressionMethod Only present if
* zipCompressionMethod is set to DEFLATE.
*/ */
/** /**
@ -91,6 +80,7 @@ export class Zipper {
* @private * @private
*/ */
this.zipCompressionMethod = options.zipCompressionMethod || ZipCompressionMethod.STORE; this.zipCompressionMethod = options.zipCompressionMethod || ZipCompressionMethod.STORE;
if (this.zipCompressionMethod === ZipCompressionMethod.DEFLATE) throw `DEFLATE not supported.`;
/** /**
* @type {CompressStatus} * @type {CompressStatus}

View file

@ -19,9 +19,9 @@ export const UnarchiveEventType = {
ERROR: 'error' ERROR: 'error'
}; };
/** // TODO: Use CustomEvent and a @template and remove these boilerplate events.
* An unarchive event.
*/ /** An unarchive event. */
export class UnarchiveEvent extends Event { export class UnarchiveEvent extends Event {
/** /**
* @param {string} type The event type. * @param {string} type The event type.
@ -31,9 +31,7 @@ export class UnarchiveEvent extends Event {
} }
} }
/** /** Updates all Unarchiver listeners that an append has occurred. */
* Updates all Archiver listeners that an append has occurred.
*/
export class UnarchiveAppendEvent extends UnarchiveEvent { export class UnarchiveAppendEvent extends UnarchiveEvent {
/** /**
* @param {number} numBytes The number of bytes appended. * @param {number} numBytes The number of bytes appended.
@ -49,9 +47,7 @@ export class UnarchiveAppendEvent extends UnarchiveEvent {
} }
} }
/** /** Useful for passing info up to the client (for debugging). */
* Useful for passing info up to the client (for debugging).
*/
export class UnarchiveInfoEvent extends UnarchiveEvent { export class UnarchiveInfoEvent extends UnarchiveEvent {
/** /**
* @param {string} msg The info message. * @param {string} msg The info message.
@ -67,9 +63,7 @@ export class UnarchiveInfoEvent extends UnarchiveEvent {
} }
} }
/** /** An unrecoverable error has occured. */
* An unrecoverable error has occured.
*/
export class UnarchiveErrorEvent extends UnarchiveEvent { export class UnarchiveErrorEvent extends UnarchiveEvent {
/** /**
* @param {string} msg The error message. * @param {string} msg The error message.
@ -85,18 +79,14 @@ export class UnarchiveErrorEvent extends UnarchiveEvent {
} }
} }
/** /** Start event. */
* Start event.
*/
export class UnarchiveStartEvent extends UnarchiveEvent { export class UnarchiveStartEvent extends UnarchiveEvent {
constructor() { constructor() {
super(UnarchiveEventType.START); super(UnarchiveEventType.START);
} }
} }
/** /** Finish event. */
* Finish event.
*/
export class UnarchiveFinishEvent extends UnarchiveEvent { export class UnarchiveFinishEvent extends UnarchiveEvent {
/** /**
* @param {Object} metadata A collection fo metadata about the archive file. * @param {Object} metadata A collection fo metadata about the archive file.
@ -108,9 +98,7 @@ export class UnarchiveFinishEvent extends UnarchiveEvent {
} }
// TODO(bitjs): Fully document these. They are confusing. // TODO(bitjs): Fully document these. They are confusing.
/** /** Progress event. */
* Progress event.
*/
export class UnarchiveProgressEvent extends UnarchiveEvent { export class UnarchiveProgressEvent extends UnarchiveEvent {
/** /**
* @param {string} currentFilename * @param {string} currentFilename
@ -136,9 +124,7 @@ export class UnarchiveProgressEvent extends UnarchiveEvent {
} }
} }
/** /** Extract event. */
* Extract event.
*/
export class UnarchiveExtractEvent extends UnarchiveEvent { export class UnarchiveExtractEvent extends UnarchiveEvent {
/** /**
* @param {UnarchivedFile} unarchivedFile * @param {UnarchivedFile} unarchivedFile

View file

@ -28,6 +28,7 @@ let hostPort;
// State - consider putting these into a class. // State - consider putting these into a class.
let unarchiveState = UnarchiveState.NOT_STARTED; let unarchiveState = UnarchiveState.NOT_STARTED;
/** @type {ByteStream} */
let bytestream = null; let bytestream = null;
let allLocalFiles = null; let allLocalFiles = null;
let logToConsole = false; let logToConsole = false;

View file

@ -24,6 +24,7 @@ let hostPort;
// State - consider putting these into a class. // State - consider putting these into a class.
let unarchiveState = UnarchiveState.NOT_STARTED; let unarchiveState = UnarchiveState.NOT_STARTED;
/** @type {ByteStream} */
let bytestream = null; let bytestream = null;
let allLocalFiles = null; let allLocalFiles = null;
let logToConsole = false; let logToConsole = false;

View file

@ -15,6 +15,9 @@
import { BitStream } from '../io/bitstream.js'; import { BitStream } from '../io/bitstream.js';
import { ByteBuffer } from '../io/bytebuffer.js'; import { ByteBuffer } from '../io/bytebuffer.js';
import { ByteStream } from '../io/bytestream.js'; import { ByteStream } from '../io/bytestream.js';
import { ARCHIVE_EXTRA_DATA_SIG, CENTRAL_FILE_HEADER_SIG, CRC32_MAGIC_NUMBER,
DATA_DESCRIPTOR_SIG, DIGITAL_SIGNATURE_SIG, END_OF_CENTRAL_DIR_SIG,
LOCAL_FILE_HEADER_SIG } from './common.js';
const UnarchiveState = { const UnarchiveState = {
NOT_STARTED: 0, NOT_STARTED: 0,
@ -28,6 +31,7 @@ let hostPort;
// State - consider putting these into a class. // State - consider putting these into a class.
let unarchiveState = UnarchiveState.NOT_STARTED; let unarchiveState = UnarchiveState.NOT_STARTED;
/** @type {ByteStream} */
let bytestream = null; let bytestream = null;
let allLocalFiles = null; let allLocalFiles = null;
let logToConsole = false; let logToConsole = false;
@ -60,14 +64,6 @@ const postProgress = function () {
}); });
}; };
const zLocalFileHeaderSignature = 0x04034b50;
const zArchiveExtraDataSignature = 0x08064b50;
const zCentralFileHeaderSignature = 0x02014b50;
const zDigitalSignatureSignature = 0x05054b50;
const zEndOfCentralDirSignature = 0x06054b50;
const zEndOfCentralDirLocatorSignature = 0x07064b50;
const zDataDescriptorSignature = 0x08074b50;
// mask for getting the Nth bit (zero-based) // mask for getting the Nth bit (zero-based)
const BIT = [0x01, 0x02, 0x04, 0x08, const BIT = [0x01, 0x02, 0x04, 0x08,
0x10, 0x20, 0x40, 0x80, 0x10, 0x20, 0x40, 0x80,
@ -75,9 +71,7 @@ const BIT = [0x01, 0x02, 0x04, 0x08,
0x1000, 0x2000, 0x4000, 0x8000]; 0x1000, 0x2000, 0x4000, 0x8000];
class ZipLocalFile { class ZipLocalFile {
/** /** @param {ByteStream} bstream */
* @param {ByteStream} bstream
*/
constructor(bstream) { constructor(bstream) {
if (typeof bstream != typeof {} || !bstream.readNumber || typeof bstream.readNumber != typeof function () { }) { if (typeof bstream != typeof {} || !bstream.readNumber || typeof bstream.readNumber != typeof function () { }) {
return null; return null;
@ -125,9 +119,9 @@ class ZipLocalFile {
let foundDataDescriptor = false; let foundDataDescriptor = false;
let numBytesSeeked = 0; let numBytesSeeked = 0;
while (!foundDataDescriptor) { while (!foundDataDescriptor) {
while (bstream.peekNumber(4) !== zLocalFileHeaderSignature && while (bstream.peekNumber(4) !== LOCAL_FILE_HEADER_SIG &&
bstream.peekNumber(4) !== zArchiveExtraDataSignature && bstream.peekNumber(4) !== ARCHIVE_EXTRA_DATA_SIG &&
bstream.peekNumber(4) !== zCentralFileHeaderSignature) { bstream.peekNumber(4) !== CENTRAL_FILE_HEADER_SIG) {
numBytesSeeked++; numBytesSeeked++;
bstream.readBytes(1); bstream.readBytes(1);
} }
@ -143,7 +137,7 @@ class ZipLocalFile {
// From the PKZIP App Note: "The signature value 0x08074b50 is also used by some ZIP // From the PKZIP App Note: "The signature value 0x08074b50 is also used by some ZIP
// implementations as a marker for the Data Descriptor record". // implementations as a marker for the Data Descriptor record".
if (maybeDescriptorSig === zDataDescriptorSignature) { if (maybeDescriptorSig === DATA_DESCRIPTOR_SIG) {
if (maybeCompressedSize === (numBytesSeeked - 16)) { if (maybeCompressedSize === (numBytesSeeked - 16)) {
foundDataDescriptor = true; foundDataDescriptor = true;
descriptorSize = 16; descriptorSize = 16;
@ -606,7 +600,7 @@ 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.
while (bstream.peekNumber(4) == zLocalFileHeaderSignature) { while (bstream.peekNumber(4) == LOCAL_FILE_HEADER_SIG) {
// Note that this could throw an error if the bstream overflows, which is caught in the // Note that this could throw an error if the bstream overflows, which is caught in the
// message handler. // message handler.
const oneLocalFile = new ZipLocalFile(bstream); const oneLocalFile = new ZipLocalFile(bstream);
@ -636,7 +630,7 @@ function archiveUnzip() {
totalFilesInArchive = allLocalFiles.length; totalFilesInArchive = allLocalFiles.length;
// archive extra data record // archive extra data record
if (bstream.peekNumber(4) == zArchiveExtraDataSignature) { if (bstream.peekNumber(4) == ARCHIVE_EXTRA_DATA_SIG) {
if (logToConsole) { if (logToConsole) {
info(' Found an Archive Extra Data Signature'); info(' Found an Archive Extra Data Signature');
} }
@ -649,13 +643,13 @@ function archiveUnzip() {
// central directory structure // central directory structure
// TODO: handle the rest of the structures (Zip64 stuff) // TODO: handle the rest of the structures (Zip64 stuff)
if (bstream.peekNumber(4) == zCentralFileHeaderSignature) { if (bstream.peekNumber(4) == CENTRAL_FILE_HEADER_SIG) {
if (logToConsole) { if (logToConsole) {
info(' Found a Central File Header'); info(' Found a Central File Header');
} }
// read all file headers // read all file headers
while (bstream.peekNumber(4) == zCentralFileHeaderSignature) { while (bstream.peekNumber(4) == CENTRAL_FILE_HEADER_SIG) {
bstream.readNumber(4); // signature bstream.readNumber(4); // signature
const cdfh = { const cdfh = {
versionMadeBy: bstream.readNumber(2), versionMadeBy: bstream.readNumber(2),
@ -688,7 +682,7 @@ function archiveUnzip() {
} }
// digital signature // digital signature
if (bstream.peekNumber(4) == zDigitalSignatureSignature) { if (bstream.peekNumber(4) == DIGITAL_SIGNATURE_SIG) {
if (logToConsole) { if (logToConsole) {
info(' Found a Digital Signature'); info(' Found a Digital Signature');
} }
@ -699,7 +693,7 @@ function archiveUnzip() {
} }
let metadata = {}; let metadata = {};
if (bstream.peekNumber(4) == zEndOfCentralDirSignature) { if (bstream.peekNumber(4) == END_OF_CENTRAL_DIR_SIG) {
bstream.readNumber(4); // signature bstream.readNumber(4); // signature
const eocds = { const eocds = {
numberOfThisDisk: bstream.readNumber(2), numberOfThisDisk: bstream.readNumber(2),

View file

@ -12,6 +12,10 @@
*/ */
import { ByteBuffer } from '../io/bytebuffer.js'; import { ByteBuffer } from '../io/bytebuffer.js';
import { CENTRAL_FILE_HEADER_SIG, CRC32_MAGIC_NUMBER, END_OF_CENTRAL_DIR_SIG,
LOCAL_FILE_HEADER_SIG } from './common.js';
/** @typedef {import('./common.js').FileInfo} FileInfo */
/** @type {MessagePort} */ /** @type {MessagePort} */
let hostPort; let hostPort;
@ -28,14 +32,6 @@ let hostPort;
* The client should append the bytes to a single buffer in the order they were received. * The client should append the bytes to a single buffer in the order they were received.
*/ */
// TODO(bitjs): De-dupe this typedef and the one in compress.js.
/**
* @typedef FileInfo An object that is sent by the client to represent a file.
* @property {string} fileName The name of this file. TODO: Includes the path?
* @property {number} lastModTime The number of ms since the Unix epoch (1970-01-01 at midnight).
* @property {Uint8Array} fileData The raw bytes of the file.
*/
// TODO(bitjs): Figure out where this typedef should live. // TODO(bitjs): Figure out where this typedef should live.
/** /**
* @typedef CompressFilesMessage A message the client sends to the implementation. * @typedef CompressFilesMessage A message the client sends to the implementation.
@ -46,12 +42,6 @@ let hostPort;
// TODO: Support DEFLATE. // TODO: Support DEFLATE.
// TODO: Support options that can let client choose levels of compression/performance. // TODO: Support options that can let client choose levels of compression/performance.
// TODO(bitjs): These constants should be defined in a common isomorphic ES module.
const zLocalFileHeaderSignature = 0x04034b50;
const zCentralFileHeaderSignature = 0x02014b50;
const zEndOfCentralDirSignature = 0x06054b50;
const zCRC32MagicNumber = 0xedb88320; // 0xdebb20e3;
/** /**
* @typedef CentralDirectoryFileHeaderInfo An object to be used to construct the central directory. * @typedef CentralDirectoryFileHeaderInfo An object to be used to construct the central directory.
* @property {string} fileName * @property {string} fileName
@ -93,7 +83,7 @@ function createCRC32Table() {
for (let n = 0; n < 256; n++) { for (let n = 0; n < 256; n++) {
let c = n; let c = n;
for (let k = 0; k < 8; k++) { for (let k = 0; k < 8; k++) {
c = ((c & 1) ? (zCRC32MagicNumber ^ (c >>> 1)) : (c >>> 1)); c = ((c & 1) ? (CRC32_MAGIC_NUMBER ^ (c >>> 1)) : (c >>> 1));
} }
table[n] = c; table[n] = c;
} }
@ -157,7 +147,7 @@ function zipOneFile(file) {
/** @type {ByteBuffer} */ /** @type {ByteBuffer} */
const buffer = new ByteBuffer(fileHeaderSize + file.fileData.byteLength); const buffer = new ByteBuffer(fileHeaderSize + file.fileData.byteLength);
buffer.writeNumber(zLocalFileHeaderSignature, 4); // Magic number. buffer.writeNumber(LOCAL_FILE_HEADER_SIG, 4); // Magic number.
buffer.writeNumber(0x0A, 2); // Version. buffer.writeNumber(0x0A, 2); // Version.
buffer.writeNumber(0, 2); // General Purpose Flags. buffer.writeNumber(0, 2); // General Purpose Flags.
buffer.writeNumber(0, 2); // Compression Method. 0 = Store only. buffer.writeNumber(0, 2); // Compression Method. 0 = Store only.
@ -201,7 +191,7 @@ function writeCentralFileDirectory() {
const buffer = new ByteBuffer(cdsLength + 22); const buffer = new ByteBuffer(cdsLength + 22);
for (const cdInfo of centralDirectoryInfos) { for (const cdInfo of centralDirectoryInfos) {
buffer.writeNumber(zCentralFileHeaderSignature, 4); // Magic number. buffer.writeNumber(CENTRAL_FILE_HEADER_SIG, 4); // Magic number.
buffer.writeNumber(0, 2); // Version made by. // 0x31e buffer.writeNumber(0, 2); // Version made by. // 0x31e
buffer.writeNumber(0, 2); // Version needed to extract (minimum). // 0x14 buffer.writeNumber(0, 2); // Version needed to extract (minimum). // 0x14
buffer.writeNumber(0, 2); // General purpose bit flag buffer.writeNumber(0, 2); // General purpose bit flag
@ -222,7 +212,7 @@ function writeCentralFileDirectory() {
} }
// 22 more bytes. // 22 more bytes.
buffer.writeNumber(zEndOfCentralDirSignature, 4); // Magic number. buffer.writeNumber(END_OF_CENTRAL_DIR_SIG, 4); // Magic number.
buffer.writeNumber(0, 2); // Number of this disk. buffer.writeNumber(0, 2); // Number of this disk.
buffer.writeNumber(0, 2); // Disk where central directory starts. buffer.writeNumber(0, 2); // Disk where central directory starts.
buffer.writeNumber(filesCompressed.length, 2); // Number of central directory records on this disk. buffer.writeNumber(filesCompressed.length, 2); // Number of central directory records on this disk.
@ -287,7 +277,7 @@ export function connect(port) {
export function disconnect() { export function disconnect() {
if (!hostPort) { if (!hostPort) {
throw `hostPort was not connected in unzip.js`; throw `hostPort was not connected in zip.js`;
} }
hostPort = null; hostPort = null;

View file

@ -65,6 +65,8 @@ export class BitBuffer {
this.bitPtr = this.mtl ? 7 : 0; this.bitPtr = this.mtl ? 7 : 0;
} }
// TODO: Be consistent with naming across classes (big-endian and little-endian).
/** @returns {boolean} */ /** @returns {boolean} */
getPackingDirection() { getPackingDirection() {
return this.mtl; return this.mtl;

View file

@ -9,6 +9,8 @@
* Copyright(c) 2011 antimatter15 * Copyright(c) 2011 antimatter15
*/ */
// TODO: Allow big-endian and little-endian, with consistent naming.
/** /**
* A write-only Byte buffer which uses a Uint8 Typed Array as a backing store. * A write-only Byte buffer which uses a Uint8 Typed Array as a backing store.
*/ */

View file

@ -1,6 +1,6 @@
{ {
"name": "@codedread/bitjs", "name": "@codedread/bitjs",
"version": "1.2.1", "version": "1.2.2",
"description": "Binary Tools for JavaScript", "description": "Binary Tools for JavaScript",
"homepage": "https://github.com/codedread/bitjs", "homepage": "https://github.com/codedread/bitjs",
"author": "Jeff Schiller", "author": "Jeff Schiller",

View file

@ -1,8 +1,9 @@
import * as fs from 'node:fs'; import * as fs from 'node:fs';
import 'mocha'; import 'mocha';
import { expect } from 'chai'; import { expect } from 'chai';
import { Unarchiver, getUnarchiver } from '../archive/decompress.js'; import { getUnarchiver } from '../archive/decompress.js';
import { CompressStatus, Zipper, ZipCompressionMethod } from '../archive/compress.js'; import { CompressStatus, Zipper } from '../archive/compress.js';
import { ZipCompressionMethod } from '../archive/common.js';
/** /**
* @typedef {import('./archive/compress.js').FileInfo} FileInfo * @typedef {import('./archive/compress.js').FileInfo} FileInfo