Skip to content

Commit

Permalink
add zlib.Gzip class
Browse files Browse the repository at this point in the history
  • Loading branch information
anonrig committed Aug 18, 2024
1 parent 3f57135 commit 1a6329f
Show file tree
Hide file tree
Showing 10 changed files with 1,328 additions and 57 deletions.
6 changes: 6 additions & 0 deletions src/node/internal/internal_errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,12 @@ export class ERR_STREAM_UNSHIFT_AFTER_END_EVENT extends NodeError {
}
}

export class ERR_BUFFER_TOO_LARGE extends NodeRangeError {
constructor(value: number) {
super("ERR_BUFFER_TOO_LARGE", `Cannot create a Buffer larger than ${value} bytes`);
}
}

export function aggregateTwoErrors(innerError: any, outerError: any) {
if (innerError && outerError && innerError !== outerError) {
if (Array.isArray(outerError.errors)) {
Expand Down
301 changes: 284 additions & 17 deletions src/node/internal/internal_zlib.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,43 @@
// Copyright (c) 2017-2022 Cloudflare, Inc.
// Licensed under the Apache 2.0 license found in the LICENSE file or at:
// https://opensource.org/licenses/Apache-2.0
// Copyright Joyent and Node contributors. All rights reserved. MIT license.

import { default as zlibUtil } from 'node-internal:zlib';
import { Buffer } from 'node-internal:internal_buffer';
import { validateUint32 } from 'node-internal:validators';
import { isArrayBufferView } from 'node-internal:internal_types';
import { validateUint32, checkRangesOrGetDefault } from 'node-internal:validators';
import { ERR_INVALID_ARG_TYPE } from 'node-internal:internal_errors';
import {
isArrayBufferView,
isAnyArrayBuffer
} from 'node-internal:internal_types';
import type { ZlibOptions } from "node:zlib";
import { ZlibBase } from 'node-internal:internal_zlib_base';
import assert from 'node:assert';

function crc32(data: ArrayBufferView | string, value: number = 0): number {
if (typeof data === 'string') {
data = Buffer.from(data);
} else if (!isArrayBufferView(data)) {
throw new ERR_INVALID_ARG_TYPE('data', 'ArrayBufferView', typeof data);
}
validateUint32(value, 'value');
return zlibUtil.crc32(data, value);
}
const {
CONST_DEFLATE,
CONST_DEFLATERAW,
CONST_INFLATE,
CONST_INFLATERAW,
CONST_GUNZIP,
CONST_GZIP,
CONST_UNZIP,
CONST_Z_DEFAULT_STRATEGY,
CONST_Z_DEFAULT_MEMLEVEL,
CONST_Z_DEFAULT_WINDOWBITS,
CONST_Z_DEFAULT_COMPRESSION,
CONST_Z_FIXED,
CONST_Z_MAX_LEVEL,
CONST_Z_MAX_MEMLEVEL,
CONST_Z_MAX_WINDOWBITS,
CONST_Z_MIN_LEVEL,
CONST_Z_MIN_MEMLEVEL,
CONST_Z_SYNC_FLUSH,
} = zlibUtil;


const constPrefix = 'CONST_';
const constants = {};
const constPrefix = 'CONST_';
export const constants: Record<string, number> = {};

// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
Object.defineProperties(constants, Object.fromEntries(Object.entries(Object.getPrototypeOf(zlibUtil))
Expand All @@ -29,7 +50,253 @@ Object.defineProperties(constants, Object.fromEntries(Object.entries(Object.getP
}])
));

export {
crc32,
constants,
export function crc32(data: ArrayBufferView | string, value: number = 0): number {
if (typeof data === 'string') {
data = Buffer.from(data);
} else if (!isArrayBufferView(data)) {
throw new ERR_INVALID_ARG_TYPE('data', 'ArrayBufferView', typeof data);
}
validateUint32(value, 'value');
return zlibUtil.crc32(data, value);
}

function _processCallback(): void {
// // This callback's context (`this`) is the `_handle` (ZCtx) object. It is
// // important to null out the values once they are no longer needed since
// // `_handle` can stay in memory long after the buffer is needed.
// const handle = this;
// const self = this[owner_symbol];
// const state = self._writeState;

// if (self.destroyed) {
// this.buffer = null;
// this.cb();
// return;
// }

// const availOutAfter = state[0];
// const availInAfter = state[1];

// const inDelta = handle.availInBefore - availInAfter;
// self.bytesWritten += inDelta;

// const have = handle.availOutBefore - availOutAfter;
// let streamBufferIsFull = false;
// if (have > 0) {
// const out = self._outBuffer.slice(self._outOffset, self._outOffset + have);
// self._outOffset += have;
// streamBufferIsFull = !self.push(out);
// } else {
// assert(have === 0, 'have should not go down');
// }

// if (self.destroyed) {
// this.cb();
// return;
// }

// // Exhausted the output buffer, or used all the input create a new one.
// if (availOutAfter === 0 || self._outOffset >= self._chunkSize) {
// handle.availOutBefore = self._chunkSize;
// self._outOffset = 0;
// self._outBuffer = Buffer.allocUnsafe(self._chunkSize);
// }

// if (availOutAfter === 0) {
// // Not actually done. Need to reprocess.
// // Also, update the availInBefore to the availInAfter value,
// // so that if we have to hit it a third (fourth, etc.) time,
// // it'll have the correct byte counts.
// handle.inOff += inDelta;
// handle.availInBefore = availInAfter;


// if (!streamBufferIsFull) {
// this.write(handle.flushFlag,
// this.buffer, // in
// handle.inOff, // in_off
// handle.availInBefore, // in_len
// self._outBuffer, // out
// self._outOffset, // out_off
// self._chunkSize); // out_len
// } else {
// const oldRead = self._read;
// self._read = (n) => {
// self._read = oldRead;
// this.write(handle.flushFlag,
// this.buffer, // in
// handle.inOff, // in_off
// handle.availInBefore, // in_len
// self._outBuffer, // out
// self._outOffset, // out_off
// self._chunkSize); // out_len
// self._read(n);
// };
// }
// return;
// }

// if (availInAfter > 0) {
// // If we have more input that should be written, but we also have output
// // space available, that means that the compression library was not
// // interested in receiving more data, and in particular that the input
// // stream has ended early.
// // This applies to streams where we don't check data past the end of
// // what was consumed; that is, everything except Gunzip/Unzip.
// self.push(null);
// }

// // Finished with the chunk.
// this.buffer = null;
// this.cb();
}

class Zlib extends ZlibBase {
public _level = CONST_Z_DEFAULT_COMPRESSION;
public _strategy = CONST_Z_DEFAULT_STRATEGY;

public constructor(options: ZlibOptions | null | undefined, mode: number) {
let windowBits = CONST_Z_DEFAULT_WINDOWBITS;
let level = CONST_Z_DEFAULT_COMPRESSION;
let memLevel = CONST_Z_DEFAULT_MEMLEVEL;
let strategy = CONST_Z_DEFAULT_STRATEGY;
let dictionary: ZlibOptions['dictionary'] | undefined = undefined;

if (options != null) {
// Special case:
// - Compression: 0 is an invalid case.
// - Decompression: 0 indicates zlib to use the window size in the header of the compressed stream.
if ((options.windowBits == null || options.windowBits === 0) && (mode === CONST_INFLATE || mode === CONST_GUNZIP || mode === CONST_UNZIP)) {
windowBits = 0;
} else {
// `{ windowBits: 8 }` is valid for DEFLATE but not for GZIP.
const min = zlibUtil.CONST_Z_MIN_WINDOWBITS + (mode === CONST_GZIP ? 1 : 0);
windowBits = checkRangesOrGetDefault(options.windowBits, 'options.windowBits', min, CONST_Z_MAX_WINDOWBITS, CONST_Z_DEFAULT_WINDOWBITS);
}

level = checkRangesOrGetDefault(options.level, 'options.level', CONST_Z_MIN_LEVEL, CONST_Z_MAX_LEVEL, CONST_Z_DEFAULT_MEMLEVEL);
memLevel = checkRangesOrGetDefault(options.memLevel, 'options.memLevel', CONST_Z_MIN_MEMLEVEL, CONST_Z_MAX_MEMLEVEL, CONST_Z_DEFAULT_MEMLEVEL);
strategy = checkRangesOrGetDefault(options.strategy, 'options.strategy', CONST_Z_DEFAULT_STRATEGY, CONST_Z_FIXED, CONST_Z_DEFAULT_STRATEGY);
dictionary = options.dictionary;

if (dictionary != null && !isArrayBufferView(dictionary)) {
if (isAnyArrayBuffer(dictionary)) {
dictionary = Buffer.from(dictionary);
} else {
throw new ERR_INVALID_ARG_TYPE('options.dictionary', ['Buffer', 'TypedArray', 'DataView', 'ArrayBuffer'], dictionary);
}
}
}


const writeState = new Uint32Array(2);
const handle = new zlibUtil.ZlibStream(mode);
// TODO(soon): Move this to constructor.
handle.initialize(windowBits, level, memLevel, strategy, writeState, _processCallback, dictionary);

super(options ?? {}, mode, handle);

this._level = level;
this._strategy = strategy;
this._handle = handle;
this._writeState = writeState;
}

public params(level: number, strategy: number, callback: () => never): void {
checkRangesOrGetDefault(level, 'level', CONST_Z_MIN_LEVEL, CONST_Z_MAX_LEVEL);
checkRangesOrGetDefault(strategy, 'strategy', CONST_Z_DEFAULT_STRATEGY, CONST_Z_FIXED);

if (this._level !== level || this._strategy !== strategy) {
this.flush(CONST_Z_SYNC_FLUSH, this.#paramsAfterFlushCallback.bind(this, level, strategy, callback));
} else {
queueMicrotask(callback);
}
}

// This callback is used by `.params()` to wait until a full flush happened
// before adjusting the parameters. In particular, the call to the native
// `params()` function should not happen while a write is currently in progress
// on the threadpool.
#paramsAfterFlushCallback(level: number, strategy: number, callback: () => void): void {
assert(this._handle, 'zlib binding closed');
this._handle.params(level, strategy);
if (!this.destroyed) {
this._level = level;
this._strategy = strategy;
callback?.();
}
}
}

export class Gzip extends Zlib {
public constructor(options: ZlibOptions) {
super(options, CONST_GZIP);
}
}

export class Gunzip extends Zlib {
public constructor(options: ZlibOptions) {
super(options, CONST_GUNZIP);
}
}

export class Deflate extends Zlib {
public constructor(options: ZlibOptions) {
super(options, CONST_DEFLATE);
}
}

export class DeflateRaw extends Zlib {
public constructor(options: ZlibOptions) {
if (options?.windowBits === 8) {
options.windowBits = 9
}
super(options, CONST_DEFLATERAW);
}
}

export class Inflate extends Zlib {
public constructor(options: ZlibOptions) {
super(options, CONST_INFLATE);
}
}

export class InflateRaw extends Zlib {
public constructor(options: ZlibOptions) {
super(options, CONST_INFLATERAW);
}
}

export class Unzip extends Zlib {
public constructor(options: ZlibOptions) {
super(options, CONST_UNZIP);
}
}

export function createGzip(options: ZlibOptions): Gzip {
return new Gzip(options);
}

export function createGunzip(options: ZlibOptions): Gunzip {
return new Gunzip(options);
}

export function createDeflate(options: ZlibOptions): Deflate {
return new Deflate(options);
}

export function createDeflateRaw(options: ZlibOptions): DeflateRaw {
return new DeflateRaw(options);
}

export function createInflate(options: ZlibOptions): Inflate {
return new Inflate(options);
}

export function createInflateRaw(options: ZlibOptions): InflateRaw {
return new InflateRaw(options);
}

export function createUnzip(options: ZlibOptions): Unzip {
return new Unzip(options);
}
Loading

0 comments on commit 1a6329f

Please sign in to comment.