Skip to content

Commit

Permalink
zlib: Add support for info: true option
Browse files Browse the repository at this point in the history
  • Loading branch information
npaun committed Sep 11, 2024
1 parent 57d205c commit ab30798
Show file tree
Hide file tree
Showing 4 changed files with 220 additions and 124 deletions.
236 changes: 158 additions & 78 deletions src/node/internal/internal_zlib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@
// Copyright Joyent and Node contributors. All rights reserved. MIT license.

import {
ZlibMode,
default as zlibUtil,
type ZlibOptions,
type CompressCallback,
type InternalCompressCallback,
type BrotliOptions,
} from 'node-internal:zlib';
import { Buffer } from 'node-internal:internal_buffer';
import { validateUint32 } from 'node-internal:validators';
import { ERR_INVALID_ARG_TYPE } from 'node-internal:internal_errors';
import { Zlib, Brotli } from 'node-internal:internal_zlib_base';
import { Zlib, Brotli, type ZlibBase } from 'node-internal:internal_zlib_base';

type ZlibResult = Buffer | { buffer: Buffer; engine: ZlibBase };
type CompressCallback = (err: Error | null, result?: ZlibResult) => void;

const {
CONST_DEFLATE,
Expand All @@ -35,71 +37,107 @@ export function crc32(
return zlibUtil.crc32(data, value);
}

function processChunk(
engine: ZlibBase,
data: ArrayBufferView | string
): ZlibResult {
return {
engine,
// TODO(soon): What is the proper way to deal with ArrayBufferView to Buffer typing issues?
buffer: engine._processChunk(
typeof data === 'string' ? Buffer.from(data) : (data as Buffer),
engine._finishFlushFlag
),
};
}

function zlibSyncImpl(
data: ArrayBufferView | string,
options: ZlibOptions,
mode: ZlibMode
): ZlibResult {
if (!options.info) {
// Fast path, where we send the data directly to C++
return Buffer.from(zlibUtil.zlibSync(data, options, mode));
}

// Else, use the Engine class in sync mode
return processChunk(new CLASS_BY_MODE[mode](options), data);
}

export function inflateSync(
data: ArrayBufferView | string,
options: ZlibOptions = {}
): Buffer {
return Buffer.from(zlibUtil.zlibSync(data, options, zlibUtil.CONST_INFLATE));
): ZlibResult {
return zlibSyncImpl(data, options, CONST_INFLATE);
}

export function deflateSync(
data: ArrayBufferView | string,
options: ZlibOptions = {}
): Buffer {
return Buffer.from(zlibUtil.zlibSync(data, options, zlibUtil.CONST_DEFLATE));
): ZlibResult {
return zlibSyncImpl(data, options, CONST_DEFLATE);
}

export function gunzipSync(
data: ArrayBufferView | string,
options: ZlibOptions = {}
): Buffer {
return Buffer.from(zlibUtil.zlibSync(data, options, zlibUtil.CONST_GUNZIP));
): ZlibResult {
return zlibSyncImpl(data, options, CONST_GUNZIP);
}

export function gzipSync(
data: ArrayBufferView | string,
options: ZlibOptions = {}
): Buffer {
return Buffer.from(zlibUtil.zlibSync(data, options, zlibUtil.CONST_GZIP));
): ZlibResult {
return zlibSyncImpl(data, options, CONST_GZIP);
}

export function inflateRawSync(
data: ArrayBufferView | string,
options: ZlibOptions = {}
): Buffer {
return Buffer.from(
zlibUtil.zlibSync(data, options, zlibUtil.CONST_INFLATERAW)
);
): ZlibResult {
return zlibSyncImpl(data, options, CONST_INFLATERAW);
}

export function deflateRawSync(
data: ArrayBufferView | string,
options: ZlibOptions = {}
): Buffer {
return Buffer.from(
zlibUtil.zlibSync(data, options, zlibUtil.CONST_DEFLATERAW)
);
): ZlibResult {
return zlibSyncImpl(data, options, CONST_DEFLATERAW);
}

export function unzipSync(
data: ArrayBufferView | string,
options: ZlibOptions = {}
): Buffer {
return Buffer.from(zlibUtil.zlibSync(data, options, zlibUtil.CONST_UNZIP));
): ZlibResult {
return zlibSyncImpl(data, options, CONST_UNZIP);
}

export function brotliDecompressSync(
data: ArrayBufferView | string,
options: BrotliOptions = {}
): Buffer {
return Buffer.from(zlibUtil.brotliDecompressSync(data, options));
): ZlibResult {
if (!options.info) {
// Fast path, where we send the data directly to C++
return Buffer.from(zlibUtil.brotliDecompressSync(data, options));
}

// Else, use the Engine class in sync mode
return processChunk(new BrotliDecompress(options), data);
}

export function brotliCompressSync(
data: ArrayBufferView | string,
options: BrotliOptions = {}
): Buffer {
return Buffer.from(zlibUtil.brotliCompressSync(data, options));
): ZlibResult {
if (!options.info) {
// Fast path, where we send the data directly to C++
return Buffer.from(zlibUtil.brotliCompressSync(data, options));
}

// Else, use the Engine class in sync mode
return processChunk(new BrotliCompress(options), data);
}

function normalizeArgs(
Expand All @@ -115,19 +153,30 @@ function normalizeArgs(
throw new ERR_INVALID_ARG_TYPE('callback', 'Function', callbackOrUndefined);
}

function wrapCallback(callback: CompressCallback): InternalCompressCallback {
return (res: Error | ArrayBuffer) => {
function processChunkCaptureError(
engine: ZlibBase,
data: ArrayBufferView | string,
cb: CompressCallback
): void {
try {
const res = processChunk(engine, data);
queueMicrotask(() => {
if (res instanceof Error) {
callback(res);
} else {
callback(null, Buffer.from(res));
}
cb(null, res);
});
};
} catch (err: unknown) {
if (err instanceof Error) {
queueMicrotask(() => {
cb(err);
});
return;
}

throw err;
}
}

export function inflate(
function zlibImpl(
mode: ZlibMode,
data: ArrayBufferView | string,
optionsOrCallback: ZlibOptions | CompressCallback,
callbackOrUndefined?: CompressCallback
Expand All @@ -136,89 +185,79 @@ export function inflate(
optionsOrCallback,
callbackOrUndefined
);
zlibUtil.zlib(data, options, zlibUtil.CONST_INFLATE, wrapCallback(callback));

if (!options.info) {
// Fast path
zlibUtil.zlib(data, options, mode, (res) => {
queueMicrotask(() => {
if (res instanceof Error) {
callback(res);
} else {
callback(null, Buffer.from(res));
}
});
});

return;
}

processChunkCaptureError(new CLASS_BY_MODE[mode](options), data, callback);
}

export function inflate(
data: ArrayBufferView | string,
optionsOrCallback: ZlibOptions | CompressCallback,
callbackOrUndefined?: CompressCallback
): void {
zlibImpl(CONST_INFLATE, data, optionsOrCallback, callbackOrUndefined);
}

export function unzip(
data: ArrayBufferView | string,
optionsOrCallback: ZlibOptions | CompressCallback,
callbackOrUndefined?: CompressCallback
): void {
const [options, callback] = normalizeArgs(
optionsOrCallback,
callbackOrUndefined
);
zlibUtil.zlib(data, options, zlibUtil.CONST_UNZIP, wrapCallback(callback));
zlibImpl(CONST_UNZIP, data, optionsOrCallback, callbackOrUndefined);
}

export function inflateRaw(
data: ArrayBufferView | string,
optionsOrCallback: ZlibOptions | CompressCallback,
callbackOrUndefined?: CompressCallback
): void {
const [options, callback] = normalizeArgs(
optionsOrCallback,
callbackOrUndefined
);
zlibUtil.zlib(
data,
options,
zlibUtil.CONST_INFLATERAW,
wrapCallback(callback)
);
zlibImpl(CONST_INFLATERAW, data, optionsOrCallback, callbackOrUndefined);
}

export function gunzip(
data: ArrayBufferView | string,
optionsOrCallback: ZlibOptions | CompressCallback,
callbackOrUndefined?: CompressCallback
): void {
const [options, callback] = normalizeArgs(
optionsOrCallback,
callbackOrUndefined
);
zlibUtil.zlib(data, options, zlibUtil.CONST_GUNZIP, wrapCallback(callback));
zlibImpl(CONST_GUNZIP, data, optionsOrCallback, callbackOrUndefined);
}

export function deflate(
data: ArrayBufferView | string,
optionsOrCallback: ZlibOptions | CompressCallback,
callbackOrUndefined?: CompressCallback
): void {
const [options, callback] = normalizeArgs(
optionsOrCallback,
callbackOrUndefined
);
zlibUtil.zlib(data, options, zlibUtil.CONST_DEFLATE, wrapCallback(callback));
zlibImpl(CONST_DEFLATE, data, optionsOrCallback, callbackOrUndefined);
}

export function deflateRaw(
data: ArrayBufferView | string,
optionsOrCallback: ZlibOptions | CompressCallback,
callbackOrUndefined?: CompressCallback
): void {
const [options, callback] = normalizeArgs(
optionsOrCallback,
callbackOrUndefined
);
zlibUtil.zlib(
data,
options,
zlibUtil.CONST_DEFLATERAW,
wrapCallback(callback)
);
zlibImpl(CONST_DEFLATERAW, data, optionsOrCallback, callbackOrUndefined);
}

export function gzip(
data: ArrayBufferView | string,
optionsOrCallback: ZlibOptions | CompressCallback,
callbackOrUndefined?: CompressCallback
): void {
const [options, callback] = normalizeArgs(
optionsOrCallback,
callbackOrUndefined
);
zlibUtil.zlib(data, options, zlibUtil.CONST_GZIP, wrapCallback(callback));
zlibImpl(CONST_GZIP, data, optionsOrCallback, callbackOrUndefined);
}

export function brotliDecompress(
Expand All @@ -230,7 +269,23 @@ export function brotliDecompress(
optionsOrCallback,
callbackOrUndefined
);
zlibUtil.brotliDecompress(data, options, wrapCallback(callback));

if (!options.info) {
// Fast path
zlibUtil.brotliDecompress(data, options, (res) => {
queueMicrotask(() => {
if (res instanceof Error) {
callback(res);
} else {
callback(null, Buffer.from(res));
}
});
});

return;
}

processChunkCaptureError(new BrotliDecompress(options), data, callback);
}

export function brotliCompress(
Expand All @@ -242,9 +297,24 @@ export function brotliCompress(
optionsOrCallback,
callbackOrUndefined
);
zlibUtil.brotliCompress(data, options, wrapCallback(callback));
}

if (!options.info) {
// Fast path
zlibUtil.brotliCompress(data, options, (res) => {
queueMicrotask(() => {
if (res instanceof Error) {
callback(res);
} else {
callback(null, Buffer.from(res));
}
});
});

return;
}

processChunkCaptureError(new BrotliCompress(options), data, callback);
}
export class Gzip extends Zlib {
public constructor(options: ZlibOptions) {
super(options, CONST_GZIP);
Expand Down Expand Up @@ -302,6 +372,16 @@ export class BrotliDecompress extends Brotli {
}
}

const CLASS_BY_MODE = {
[ZlibMode.DEFLATE]: Deflate,
[ZlibMode.INFLATE]: Inflate,
[ZlibMode.DEFLATERAW]: DeflateRaw,
[ZlibMode.INFLATERAW]: InflateRaw,
[ZlibMode.GZIP]: Gzip,
[ZlibMode.GUNZIP]: Gunzip,
[ZlibMode.UNZIP]: Unzip,
};

export function createGzip(options: ZlibOptions): Gzip {
return new Gzip(options);
}
Expand Down
10 changes: 10 additions & 0 deletions src/node/internal/internal_zlib_base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,16 @@ export class ZlibBase extends Transform {
}

// This function is left for backwards compatibility.
public _processChunk(
chunk: Buffer,
flushFlag: number,
cb?: undefined
): Buffer;
public _processChunk(
chunk: Buffer,
flushFlag: number,
cb: () => void
): undefined;
public _processChunk(
chunk: Buffer,
flushFlag: number,
Expand Down
Loading

0 comments on commit ab30798

Please sign in to comment.