Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/js/builtins.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,7 @@ declare function $ERR_STREAM_CANNOT_PIPE(): Error;
declare function $ERR_STREAM_WRITE_AFTER_END(): Error;
declare function $ERR_STREAM_UNSHIFT_AFTER_END_EVENT(): Error;
declare function $ERR_STREAM_PUSH_AFTER_EOF(): Error;
declare function $ERR_TRAILING_JUNK_AFTER_STREAM_END(): TypeError;
declare function $ERR_STREAM_UNABLE_TO_PIPE(): Error;
declare function $ERR_ILLEGAL_CONSTRUCTOR(): TypeError;
declare function $ERR_SERVER_ALREADY_LISTEN(): Error;
Expand Down
44 changes: 41 additions & 3 deletions src/js/node/zlib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ const kMaxLength = $requireMap.$get("buffer")?.exports.kMaxLength ?? BufferModul

const { Transform, finished } = require("node:stream");
const owner_symbol = Symbol("owner_symbol");
const { checkRangesOrGetDefault, validateFunction, validateFiniteNumber } = require("internal/validators");
const {
checkRangesOrGetDefault,
validateBoolean,
validateFunction,
validateFiniteNumber,
} = require("internal/validators");

const kFlushFlag = Symbol("kFlushFlag");
const kAfterFlush = Symbol("kAfterFlush");
Expand Down Expand Up @@ -173,7 +178,7 @@ interface ZlibHandle extends $Bindgen.CompressionStream {
[owner_symbol]: $Node.ZlibBase;
/** Set only while a write is on the threadpool. */
buffer: (View & { [kAfterFlush]?: () => void }) | null;
cb: () => void;
cb: (error?: Error) => void;
afterWrite?: (() => void) | null;
availOutBefore: number;
availInBefore: number;
Expand All @@ -199,6 +204,7 @@ interface ZlibOptions extends Omit<$Node.TransformOptions, "flush" | "encoding">
dictionary?: View | ArrayBuffer | SharedArrayBuffer;
info?: boolean;
maxOutputLength?: number;
rejectGarbageAfterEnd?: boolean;
params?: Record<string, number | boolean>;
pledgedSrcSize?: number;
}
Expand All @@ -219,6 +225,7 @@ declare global {
_defaultFullFlushFlag: number;
_info: boolean | undefined;
_maxOutputLength: number;
_rejectGarbageAfterEnd: boolean;
reset(): void;
flush(kind?: number | (() => void), callback?: () => void): void;
close(callback?: () => void): void;
Expand Down Expand Up @@ -252,6 +259,7 @@ function ZlibBase(
) {
let chunkSize = Z_DEFAULT_CHUNK;
let maxOutputLength = kMaxLength;
let rejectGarbageAfterEnd: boolean | undefined;
// The ZlibBase class is not exported to user land, the mode should only be passed in by us.
$assert(typeof mode === "number");
$assert(mode >= DEFLATE && mode <= ZSTD_DECOMPRESS);
Expand Down Expand Up @@ -280,6 +288,11 @@ function ZlibBase(
// prettier-ignore
maxOutputLength = checkRangesOrGetDefault(opts.maxOutputLength, "options.maxOutputLength", 1, kMaxLength, kMaxLength);

rejectGarbageAfterEnd = opts.rejectGarbageAfterEnd;
if (rejectGarbageAfterEnd !== undefined) {
validateBoolean(rejectGarbageAfterEnd, "options.rejectGarbageAfterEnd");
}

if (opts.encoding || opts.objectMode || opts.writableObjectMode) {
opts = { ...opts };
opts.encoding = null;
Expand All @@ -304,6 +317,8 @@ function ZlibBase(
this._defaultFullFlushFlag = fullFlush;
this._info = opts && opts.info;
this._maxOutputLength = maxOutputLength;

this._rejectGarbageAfterEnd = rejectGarbageAfterEnd === true;
}
$toClass(ZlibBase, "ZlibBase", Transform);
ZlibBase.prototype[kFlushBound] = FLUSH_BOUND[FLUSH_BOUND_IDX_NORMAL];
Expand Down Expand Up @@ -518,6 +533,11 @@ function processChunkSync(self: $Node.ZlibBase, chunk: View, flushFlag: number):
}
}

if (availInAfter > 0 && self._rejectGarbageAfterEnd) {
Comment thread
robobun marked this conversation as resolved.
Resolved
_close(self);
throw $ERR_TRAILING_JUNK_AFTER_STREAM_END();
}

self.bytesWritten = inputRead;
_close(self);

Expand Down Expand Up @@ -647,6 +667,15 @@ function processCallback(this: ZlibHandle) {
// This applies to streams where we don't check data past the end of
// what was consumed; that is, everything except Gunzip/Unzip.

if (self._rejectGarbageAfterEnd) {
const err = $ERR_TRAILING_JUNK_AFTER_STREAM_END();
self.destroy(err);
// Node.js leaves the chunk on the handle here.
this.buffer = null;
this.cb(err);
return;
}

self.push(null);
}

Expand Down Expand Up @@ -718,7 +747,16 @@ function Zlib(this: $Node.Zlib, opts: ZlibOptions | undefined, mode: number) {

const handle = new NativeZlib(mode);
this._writeState = new Uint32Array(2);
handle.init(windowBits, level, memLevel, strategy, this._writeState, processCallback, dictionary);
handle.init(
windowBits,
level,
memLevel,
strategy,
this._writeState,
processCallback,
dictionary,
opts?.rejectGarbageAfterEnd === true,
);

ZlibBase.$apply(this, [opts, mode, handle, zlibDefaultOpts]);

Expand Down
1 change: 1 addition & 0 deletions src/js/private.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2127,6 +2127,7 @@ declare namespace $Bindgen {
writeResult: Uint32Array,
writeCallback: () => void,
dictionary: $Node.AnyView | undefined,
rejectGarbageAfterEnd?: boolean,
): void;
}
interface NativeBrotli extends CompressionStream {
Expand Down
1 change: 1 addition & 0 deletions src/runtime/node/node_zlib_binding.bind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export const NativeZlib = compressionStream(
writeState: b.any,
processCallback: b.any,
dictionary: b.any,
rejectGarbageAfterEnd: b.bool.default(false),
},
},
{ ...method, args: { level: int32, strategy: int32 } },
Expand Down
14 changes: 12 additions & 2 deletions src/runtime/node/zlib/NativeZlib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ mod _impl {
write_state: JsLocal<'_>,
process_callback: JsLocal<'_>,
dictionary: JsLocal<'_>,
reject_garbage_after_end: bool,
) -> JsResult<()> {
let global = cx.global();
let this_value = this_value.to_js();
Expand Down Expand Up @@ -162,8 +163,10 @@ mod _impl {
write_callback.with_async_context_if_needed(global),
);

self.stream
.with_mut(|s| s.init(level, window_bits, mem_level, strategy, dictionary));
self.stream.with_mut(|s| {
s.reject_garbage_after_end = reject_garbage_after_end;
s.init(level, window_bits, mem_level, strategy, dictionary)
});
// `Context::init` leaves `mode` at `NONE` when `deflateInit2_` /
// `inflateInit2_` rejects its arguments; mark the wrapper closed so
// the next operation is rejected instead of re-entering `NONE`.
Expand Down Expand Up @@ -218,8 +221,13 @@ pub(crate) struct Context {
pub flush: c::FlushValue,
pub dictionary: Vec<u8>,
pub(crate) gzip_id_bytes_read: u8,
/// `options.rejectGarbageAfterEnd`: input after the first gzip member stays unconsumed, and `zlib.ts` refuses it.
pub(crate) reject_garbage_after_end: bool,
}

// The flag sits in the padding after `gzip_id_bytes_read`.
const _: () = assert!(core::mem::size_of::<Context>() == 48);

impl Default for Context {
fn default() -> Self {
Self {
Expand All @@ -229,6 +237,7 @@ impl Default for Context {
flush: c::FlushValue::NoFlush,
dictionary: Vec::new(),
gzip_id_bytes_read: 0,
reject_garbage_after_end: false,
}
}
}
Expand Down Expand Up @@ -506,6 +515,7 @@ impl Context {
while self.state.avail_in() > 0
&& self.mode == c::NodeMode::GUNZIP
&& self.err == c::ReturnCode::StreamEnd
&& !self.reject_garbage_after_end
// SAFETY: avail_in > 0 ⇒ next_in points to ≥1 readable byte.
&& unsafe { *self.state.next_in() } != 0
{
Expand Down
Loading
Loading