-
Notifications
You must be signed in to change notification settings - Fork 294
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
406 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,212 @@ | ||
import { ZlibMode, default as zlibUtil } from 'node-internal:zlib'; | ||
import { Buffer } from 'node-internal:internal_buffer'; | ||
import { validateUint32, checkRangesOrGetDefault, checkFiniteNumber } from 'node-internal:validators'; | ||
import { ERR_INVALID_ARG_TYPE, ERR_OUT_OF_RANGE } from 'node-internal:internal_errors'; | ||
import { | ||
isArrayBufferView, | ||
} from 'node-internal:internal_types'; | ||
import { kMaxLength } from 'node-internal:internal_buffer'; | ||
import { Transform } from 'node-internal:streams_transform' | ||
import type { ZlibOptions } from "node:zlib"; | ||
import type { DuplexOptions } from 'node:stream'; | ||
|
||
const { | ||
CONST_BROTLI_DECODE, | ||
CONST_BROTLI_ENCODE, | ||
CONST_BROTLI_OPERATION_EMIT_METADATA, | ||
CONST_BROTLI_OPERATION_PROCESS, | ||
CONST_Z_BLOCK, | ||
CONST_Z_DEFAULT_CHUNK, | ||
CONST_Z_MIN_CHUNK, | ||
CONST_Z_NO_FLUSH, | ||
CONST_Z_FINISH, | ||
CONST_Z_FULL_FLUSH, | ||
CONST_Z_SYNC_FLUSH, | ||
CONST_Z_PARTIAL_FLUSH, | ||
} = zlibUtil; | ||
|
||
const FLUSH_BOUND = [ | ||
[ CONST_Z_NO_FLUSH, CONST_Z_BLOCK ], | ||
[ CONST_BROTLI_OPERATION_PROCESS, CONST_BROTLI_OPERATION_EMIT_METADATA ], | ||
] as const; | ||
const FLUSH_BOUND_IDX_NORMAL: number = 0; | ||
const FLUSH_BOUND_IDX_BROTLI: number = 1; | ||
|
||
const kFlushFlag = Symbol('kFlushFlag'); | ||
const kError = Symbol('kError'); | ||
|
||
// If a flush is scheduled while another flush is still pending, a way to figure | ||
// out which one is the "stronger" flush is needed. | ||
// This is currently only used to figure out which flush flag to use for the | ||
// last chunk. | ||
// Roughly, the following holds: | ||
// Z_NO_FLUSH (< Z_TREES) < Z_BLOCK < Z_PARTIAL_FLUSH < | ||
// Z_SYNC_FLUSH < Z_FULL_FLUSH < Z_FINISH | ||
const flushiness: number[] = []; | ||
const kFlushFlagList = [CONST_Z_NO_FLUSH, CONST_Z_BLOCK, CONST_Z_PARTIAL_FLUSH, | ||
CONST_Z_SYNC_FLUSH, CONST_Z_FULL_FLUSH, CONST_Z_FINISH]; | ||
for (let i = 0; i < kFlushFlagList.length; i++) { | ||
flushiness[kFlushFlagList[i] as number] = i; | ||
} | ||
|
||
type BufferWithFlushFlag = Buffer & { [kFlushFlag]: number}; | ||
|
||
// Set up a list of 'special' buffers that can be written using .write() | ||
// from the .flush() code as a way of introducing flushing operations into the | ||
// write sequence. | ||
const kFlushBuffers: BufferWithFlushFlag[] = []; | ||
{ | ||
const dummyArrayBuffer = new ArrayBuffer(0); | ||
for (const flushFlag of kFlushFlagList) { | ||
const buf = Buffer.from(dummyArrayBuffer) as BufferWithFlushFlag; | ||
buf[kFlushFlag] = flushFlag; | ||
kFlushBuffers[flushFlag] = buf; | ||
} | ||
} | ||
|
||
type ZlibBaseDefaultOptions = { | ||
flush?: number | undefined; | ||
finishFlush?: number | undefined; | ||
fullFlush?: number | undefined | ||
} | ||
|
||
const zlibDefaultOptions = { | ||
flush: CONST_Z_NO_FLUSH, | ||
finishFlush: CONST_Z_FINISH, | ||
fullFlush: CONST_Z_FULL_FLUSH, | ||
}; | ||
|
||
export class ZlibBase extends Transform { | ||
public bytesWritten: number = 0; | ||
|
||
public _maxOutputLength: number; | ||
public _outBuffer: Buffer; | ||
public _outOffset: number = 0; | ||
public _chunkSize: number; | ||
public _defaultFlushFlag: number | undefined; | ||
public _finishFlushFlag: number | undefined; | ||
public _defaultFullFlushFlag: number | undefined; | ||
public _info: unknown; | ||
public _handle: zlibUtil.ZlibHandle; | ||
|
||
public [kError]: null; | ||
|
||
public constructor(opts: ZlibOptions & DuplexOptions, mode: ZlibMode, handle: zlibUtil.ZlibHandle, { flush, finishFlush, fullFlush }: ZlibBaseDefaultOptions = zlibDefaultOptions) { | ||
let chunkSize = CONST_Z_DEFAULT_CHUNK; | ||
let maxOutputLength = kMaxLength; | ||
|
||
let flushBoundIdx; | ||
if (mode !== CONST_BROTLI_ENCODE && mode !== CONST_BROTLI_DECODE) { | ||
flushBoundIdx = FLUSH_BOUND_IDX_NORMAL; | ||
} else { | ||
flushBoundIdx = FLUSH_BOUND_IDX_BROTLI; | ||
} | ||
|
||
if (opts) { | ||
if (opts.chunkSize != null) { | ||
chunkSize = opts.chunkSize; | ||
} | ||
if (!checkFiniteNumber(chunkSize, 'options.chunkSize')) { | ||
chunkSize = CONST_Z_DEFAULT_CHUNK; | ||
} else if (chunkSize < CONST_Z_MIN_CHUNK) { | ||
throw new ERR_OUT_OF_RANGE('options.chunkSize', | ||
`>= ${CONST_Z_MIN_CHUNK}`, chunkSize); | ||
} | ||
|
||
flush = checkRangesOrGetDefault( | ||
opts.flush, 'options.flush', | ||
FLUSH_BOUND[flushBoundIdx]![0], FLUSH_BOUND[flushBoundIdx]![1], flush as number); | ||
|
||
finishFlush = checkRangesOrGetDefault( | ||
opts.finishFlush, 'options.finishFlush', | ||
FLUSH_BOUND[flushBoundIdx]![0], FLUSH_BOUND[flushBoundIdx]![1], | ||
finishFlush as number); | ||
|
||
maxOutputLength = checkRangesOrGetDefault( | ||
opts.maxOutputLength, 'options.maxOutputLength', | ||
1, kMaxLength, kMaxLength); | ||
|
||
if (opts.encoding || opts.objectMode || opts.writableObjectMode) { | ||
opts = { ...opts }; | ||
opts.encoding = undefined; | ||
opts.objectMode = false; | ||
opts.writableObjectMode = false; | ||
} | ||
} | ||
|
||
// TODO: Find a way to avoid having "any" | ||
super({ autoDestroy: true, ...opts } as any); | ||
this[kError] = null; | ||
this._handle = handle; | ||
// handle[owner_symbol] = this; | ||
// Used by processCallback() and zlibOnError() | ||
// handle.onerror = zlibOnError; | ||
this._outBuffer = Buffer.allocUnsafe(chunkSize); | ||
this._outOffset = 0; | ||
|
||
this._chunkSize = chunkSize; | ||
this._defaultFlushFlag = flush; | ||
this._finishFlushFlag = finishFlush; | ||
this._defaultFullFlushFlag = fullFlush; | ||
this._info = opts && opts.info; | ||
this._maxOutputLength = maxOutputLength; | ||
} | ||
|
||
public flush(kind: number | (() => void), callback: (() => void) | undefined = undefined): void { | ||
if (typeof kind === 'function' || (kind == null && !callback)) { | ||
callback = kind; | ||
kind = this._defaultFlushFlag as number; | ||
} | ||
|
||
if (this.writableFinished) { | ||
if (callback) { | ||
queueMicrotask(callback); | ||
} | ||
} else if (this.writableEnded) { | ||
if (callback) { | ||
queueMicrotask(callback); | ||
} | ||
} else { | ||
// encoding is not used. utf8 is passed to conform to typescript. | ||
this.write(kFlushBuffers[kind], 'utf8', callback); | ||
} | ||
} | ||
|
||
public close(callback?: (() => never)): void { | ||
if (callback) { | ||
finished(this, callback); | ||
} | ||
this.destroy(); | ||
} | ||
|
||
public override _destroy<T extends Error>(err: T, callback: (err: T) => never): void { | ||
_close(this); | ||
callback(err); | ||
} | ||
|
||
public override _transform(chunk: Buffer & { [kFlushFlag]?: number }, _encoding: BufferEncoding, cb: (args: unknown) => never): void { | ||
let flushFlag = this._defaultFlushFlag; | ||
// We use a 'fake' zero-length chunk to carry information about flushes from | ||
// the public API to the actual stream implementation. | ||
if (typeof chunk[kFlushFlag] === 'number') { | ||
flushFlag = chunk[kFlushFlag]; | ||
} | ||
|
||
// For the last chunk, also apply `_finishFlushFlag`. | ||
if (this.writableEnded && this.writableLength === chunk.byteLength) { | ||
flushFlag = maxFlush(flushFlag, this._finishFlushFlag); | ||
} | ||
processChunk(this, chunk, flushFlag, cb); | ||
} | ||
|
||
public _processChunk(chunk: Buffer, flushFlag: number, cb?: () => never): number | undefined { | ||
if (typeof cb === 'function') { | ||
processChunk(this, chunk, flushFlag, cb); | ||
} else { | ||
return processChunkSync(this, chunk, flushFlag); | ||
} | ||
} | ||
} | ||
|
||
Object.setPrototypeOf(ZlibBase.prototype, Transform.prototype); | ||
Object.setPrototypeOf(ZlibBase, Transform); |
Oops, something went wrong.