diff --git a/.vscode/settings.json b/.vscode/settings.json index 58ded00a0a..0fd8800e63 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -52,12 +52,15 @@ "cmake.configureOnOpen": false, "C_Cpp.errorSquiggles": "enabled", "[cpp]": { + "editor.tabSize": 4, "editor.defaultFormatter": "xaver.clang-format", }, "[c]": { + "editor.tabSize": 4, "editor.defaultFormatter": "xaver.clang-format", }, "[h]": { + "editor.tabSize": 4, "editor.defaultFormatter": "xaver.clang-format", }, "clangd.arguments": ["-header-insertion=never"], diff --git a/src/brotli.zig b/src/brotli.zig index 0759261d46..464d4b55c8 100644 --- a/src/brotli.zig +++ b/src/brotli.zig @@ -9,7 +9,7 @@ const BrotliEncoder = c.BrotliEncoder; const mimalloc = bun.Mimalloc; -const BrotliAllocator = struct { +pub const BrotliAllocator = struct { pub fn alloc(_: ?*anyopaque, len: usize) callconv(.C) *anyopaque { if (bun.heap_breakdown.enabled) { const zone = bun.heap_breakdown.getZone("brotli"); diff --git a/src/bun.js/api/js_brotli.zig b/src/bun.js/api/js_brotli.zig deleted file mode 100644 index 07bee73bda..0000000000 --- a/src/bun.js/api/js_brotli.zig +++ /dev/null @@ -1,811 +0,0 @@ -const bun = @import("root").bun; -const JSC = bun.JSC; -const std = @import("std"); -const brotli = bun.brotli; - -const Queue = std.fifo.LinearFifo(JSC.Node.BlobOrStringOrBuffer, .Dynamic); - -// We cannot free outside the JavaScript thread. -const FreeList = struct { - write_lock: bun.Lock = .{}, - list: std.ArrayListUnmanaged(JSC.Node.BlobOrStringOrBuffer) = .{}, - - pub fn append(this: *FreeList, slice: []const JSC.Node.BlobOrStringOrBuffer) void { - this.write_lock.lock(); - defer this.write_lock.unlock(); - this.list.appendSlice(bun.default_allocator, slice) catch bun.outOfMemory(); - } - - pub fn drain(this: *FreeList) void { - this.write_lock.lock(); - defer this.write_lock.unlock(); - const out = this.list.items; - for (out) |*item| { - item.deinitAndUnprotect(); - } - this.list.clearRetainingCapacity(); - } - - pub fn deinit(this: *FreeList) void { - this.drain(); - this.list.deinit(bun.default_allocator); - } -}; - -pub const BrotliEncoder = struct { - pub usingnamespace bun.New(@This()); - pub usingnamespace JSC.Codegen.JSBrotliEncoder; - - stream: brotli.BrotliCompressionStream, - chunkSize: c_uint, - maxOutputLength: usize, - - freelist: FreeList = .{}, - - globalThis: *JSC.JSGlobalObject, - mode: u8, - - input: Queue = Queue.init(bun.default_allocator), - input_lock: bun.Lock = .{}, - - has_called_end: bool = false, - callback_value: JSC.Strong = .{}, - - output: std.ArrayListUnmanaged(u8) = .{}, - output_lock: bun.Lock = .{}, - - has_pending_activity: std.atomic.Value(u32) = std.atomic.Value(u32).init(0), - pending_encode_job_count: std.atomic.Value(u32) = std.atomic.Value(u32).init(0), - ref_count: u32 = 1, - write_failure: ?JSC.DeferredError = null, - poll_ref: bun.Async.KeepAlive = .{}, - closed: bool = false, - - pub fn hasPendingActivity(this: *BrotliEncoder) bool { - return this.has_pending_activity.load(.monotonic) > 0; - } - - pub fn constructor(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) ?*BrotliEncoder { - globalThis.throw("BrotliEncoder is not constructable", .{}); - return null; - } - - pub fn create(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSC.JSValue { - const arguments = callframe.arguments(4).slice(); - - if (arguments.len < 4) { - globalThis.throwNotEnoughArguments("BrotliEncoder", 4, arguments.len); - return .zero; - } - - const opts = arguments[0]; - const callback = arguments[2]; - const mode = arguments[3].to(u8); - - const chunkSize = globalThis.getInteger(opts, u32, 1024 * 48, .{ .min = 64, .field_name = "chunkSize" }) orelse return .zero; - const maxOutputLength = globalThis.getInteger(opts, usize, 0, .{ .max = std.math.maxInt(u52), .field_name = "maxOutputLength" }) orelse return .zero; - const flush = globalThis.getInteger(opts, u8, 0, .{ .max = 3, .field_name = "flush" }) orelse return .zero; - const finishFlush = globalThis.getInteger(opts, u8, 2, .{ .max = 3, .field_name = "finishFlush" }) orelse return .zero; - const fullFlush = globalThis.getInteger(opts, u8, 1, .{ .max = 3, .field_name = "fullFlush" }) orelse return .zero; - - var this: *BrotliEncoder = BrotliEncoder.new(.{ - .globalThis = globalThis, - .stream = brotli.BrotliCompressionStream.init(@enumFromInt(flush), @enumFromInt(finishFlush), @enumFromInt(fullFlush)) catch { - globalThis.throw("Failed to create BrotliEncoder", .{}); - return .zero; - }, - .chunkSize = chunkSize, - .maxOutputLength = maxOutputLength, - .mode = mode, - }); - - if (opts.get(globalThis, "params")) |params| { - inline for (std.meta.fields(bun.brotli.c.BrotliEncoderParameter)) |f| { - if (!params.isObject()) break; - if (params.hasOwnPropertyValue(globalThis, JSC.ZigString.static(std.fmt.comptimePrint("{d}", .{f.value})).toJS(globalThis))) { - const idx = params.getIndex(globalThis, f.value); - if (!idx.isNumber()) { - globalThis.throwValue(globalThis.ERR_INVALID_ARG_TYPE_static( - JSC.ZigString.static("options.params[key]"), - JSC.ZigString.static("number"), - idx, - )); - this.deinit(); - return .zero; - } - const was_set = this.stream.brotli.setParameter(@enumFromInt(f.value), idx.toU32()); - if (!was_set) { - globalThis.ERR_ZLIB_INITIALIZATION_FAILED("Initialization failed", .{}).throw(); - this.deinit(); - return .zero; - } - } - } - } - if (globalThis.hasException()) return .zero; - - const out = this.toJS(globalThis); - this.callback_value.set(globalThis, callback); - - return out; - } - - pub fn finalize(this: *BrotliEncoder) void { - this.deinit(); - } - - pub fn deinit(this: *BrotliEncoder) void { - this.callback_value.deinit(); - this.freelist.deinit(); - this.output.deinit(bun.default_allocator); - this.stream.deinit(); - this.input.deinit(); - this.destroy(); - } - - fn drainFreelist(this: *BrotliEncoder) void { - this.freelist.drain(); - } - - fn collectOutputValue(this: *BrotliEncoder) JSC.JSValue { - this.output_lock.lock(); - defer this.output_lock.unlock(); - - defer this.output.clearRetainingCapacity(); - return JSC.ArrayBuffer.createBuffer(this.globalThis, this.output.items); - } - - pub fn runFromJSThread(this: *BrotliEncoder) void { - this.poll_ref.unref(this.globalThis.bunVM()); - - defer _ = this.has_pending_activity.fetchSub(1, .monotonic); - this.drainFreelist(); - - _ = this.callback_value.get().?.call( - this.globalThis, - .undefined, - if (this.write_failure != null) - &.{this.write_failure.?.toError(this.globalThis)} - else - &.{ .null, this.collectOutputValue() }, - ) catch |err| this.globalThis.reportActiveExceptionAsUnhandled(err); - } - - // We can only run one encode job at a time - // But we don't have an idea of a serial dispatch queue - // So instead, we let you enqueue as many times as you want - // and if one is already running, we just don't do anything - const EncodeJob = struct { - task: JSC.WorkPoolTask = .{ .callback = &runTask }, - encoder: *BrotliEncoder, - is_async: bool, - vm: *JSC.VirtualMachine, - - pub usingnamespace bun.New(@This()); - - pub fn runTask(this: *JSC.WorkPoolTask) void { - var job: *EncodeJob = @fieldParentPtr("task", this); - job.run(); - job.destroy(); - } - - pub fn run(this: *EncodeJob) void { - defer { - _ = this.encoder.has_pending_activity.fetchSub(1, .monotonic); - } - - var any = false; - - if (this.encoder.pending_encode_job_count.fetchAdd(1, .monotonic) >= 0) { - const is_last = this.encoder.has_called_end; - while (true) { - this.encoder.input_lock.lock(); - defer this.encoder.input_lock.unlock(); - const readable = this.encoder.input.readableSlice(0); - defer this.encoder.input.discard(readable.len); - const pending = readable; - - const Writer = struct { - encoder: *BrotliEncoder, - - pub const Error = error{OutOfMemory}; - pub fn writeAll(writer: @This(), chunk: []const u8) Error!void { - writer.encoder.output_lock.lock(); - defer writer.encoder.output_lock.unlock(); - - try writer.encoder.output.appendSlice(bun.default_allocator, chunk); - } - }; - - defer { - this.encoder.freelist.append(pending); - } - for (pending) |*input| { - var writer = this.encoder.stream.writer(Writer{ .encoder = this.encoder }); - writer.writeAll(input.slice()) catch { - _ = this.encoder.pending_encode_job_count.fetchSub(1, .monotonic); - if (!this.is_async) { - this.encoder.closed = true; - this.encoder.globalThis.throw("BrotliError", .{}); - return; - } - this.encoder.write_failure = JSC.DeferredError.from(.plainerror, .ERR_OPERATION_FAILED, "BrotliError", .{}); // TODO propogate better error - return; - }; - if (this.encoder.output.items.len > this.encoder.maxOutputLength) { - _ = this.encoder.pending_encode_job_count.fetchSub(1, .monotonic); - this.encoder.write_failure = JSC.DeferredError.from(.rangeerror, .ERR_BUFFER_TOO_LARGE, "Cannot create a Buffer larger than {d} bytes", .{this.encoder.maxOutputLength}); - return; - } - } - - any = any or pending.len > 0; - - if (this.encoder.pending_encode_job_count.fetchSub(1, .monotonic) == 0) - break; - } - - if (is_last and any) { - var output = &this.encoder.output; - this.encoder.output_lock.lock(); - defer this.encoder.output_lock.unlock(); - - output.appendSlice(bun.default_allocator, this.encoder.stream.end() catch { - _ = this.encoder.pending_encode_job_count.fetchSub(1, .monotonic); - this.encoder.write_failure = JSC.DeferredError.from(.plainerror, .ERR_OPERATION_FAILED, "BrotliError", .{}); // TODO propogate better error - return; - }) catch { - _ = this.encoder.pending_encode_job_count.fetchSub(1, .monotonic); - this.encoder.write_failure = JSC.DeferredError.from(.plainerror, .ERR_OPERATION_FAILED, "BrotliError", .{}); // TODO propogate better error - return; - }; - if (output.items.len > this.encoder.maxOutputLength) { - _ = this.encoder.pending_encode_job_count.fetchSub(1, .monotonic); - this.encoder.write_failure = JSC.DeferredError.from(.rangeerror, .ERR_BUFFER_TOO_LARGE, "Cannot create a Buffer larger than {d} bytes", .{this.encoder.maxOutputLength}); - return; - } - } - } - - if (this.is_async and any) { - _ = this.encoder.has_pending_activity.fetchAdd(1, .monotonic); - this.vm.enqueueTaskConcurrent(JSC.ConcurrentTask.create(JSC.Task.init(this.encoder))); - } - } - }; - - pub fn transform(this: *BrotliEncoder, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSC.JSValue { - const arguments = callframe.arguments(3); - - if (arguments.len < 3) { - globalThis.throwNotEnoughArguments("BrotliEncoder.encode", 3, arguments.len); - return .zero; - } - - if (this.has_called_end) { - globalThis.throw("BrotliEncoder.encode called after BrotliEncoder.end", .{}); - return .zero; - } - - const input = callframe.argument(0); - const optional_encoding = callframe.argument(1); - const is_last = callframe.argument(2).toBoolean(); - - const input_to_queue = JSC.Node.BlobOrStringOrBuffer.fromJSWithEncodingValueMaybeAsync(globalThis, bun.default_allocator, input, optional_encoding, true) orelse { - globalThis.throwInvalidArgumentType("BrotliEncoder.encode", "input", "Blob, String, or Buffer"); - return .zero; - }; - - _ = this.has_pending_activity.fetchAdd(1, .monotonic); - if (is_last) - this.has_called_end = true; - - var task = EncodeJob.new(.{ - .encoder = this, - .is_async = true, - .vm = this.globalThis.bunVM(), - }); - - { - this.input_lock.lock(); - defer this.input_lock.unlock(); - - // need to protect because no longer on the stack. unprotected in FreeList.deinit - input_to_queue.protect(); - this.input.writeItem(input_to_queue) catch bun.outOfMemory(); - } - this.poll_ref.ref(task.vm); - JSC.WorkPool.schedule(&task.task); - - return .undefined; - } - - pub fn transformSync(this: *BrotliEncoder, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSC.JSValue { - const arguments = callframe.arguments(4); - - if (arguments.len < 3) { - globalThis.throwNotEnoughArguments("BrotliEncoder.encode", 3, arguments.len); - return .zero; - } - - if (this.has_called_end) { - globalThis.throw("BrotliEncoder.encode called after BrotliEncoder.end", .{}); - return .zero; - } - - const input = callframe.argument(0); - const optional_encoding = callframe.argument(1); - const is_last = callframe.argument(2).toBoolean(); - const optional_flushFlag = arguments.ptr[3]; - - const old_flushFlag = this.stream.flushOp; - defer this.stream.flushOp = old_flushFlag; - blk: { - if (!optional_flushFlag.isInt32()) break :blk; - const int = optional_flushFlag.asInt32(); - if (int < 0) break :blk; - if (int > 3) break :blk; - this.stream.flushOp = @enumFromInt(int); - } - - const input_to_queue = JSC.Node.BlobOrStringOrBuffer.fromJSWithEncodingValueMaybeAsync(globalThis, bun.default_allocator, input, optional_encoding, true) orelse { - globalThis.throwInvalidArgumentType("BrotliEncoder.encode", "input", "Blob, String, or Buffer"); - return .zero; - }; - - _ = this.has_pending_activity.fetchAdd(1, .monotonic); - if (is_last) - this.has_called_end = true; - - var task: EncodeJob = .{ - .encoder = this, - .is_async = false, - .vm = this.globalThis.bunVM(), - }; - - { - this.input_lock.lock(); - defer this.input_lock.unlock(); - - // need to protect because no longer on the stack. unprotected in FreeList.deinit - input_to_queue.protect(); - this.input.writeItem(input_to_queue) catch bun.outOfMemory(); - } - task.run(); - if (!is_last and this.output.items.len == 0) { - return JSC.Buffer.fromBytes(&.{}, bun.default_allocator, .Uint8Array).toNodeBuffer(globalThis); - } - if (this.write_failure != null) { - globalThis.vm().throwError(globalThis, this.write_failure.?.toError(globalThis)); - return .zero; - } - return this.collectOutputValue(); - } - - pub fn reset(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSC.JSValue { - _ = this; - _ = globalThis; - _ = callframe; - return .undefined; - } - - pub fn getBytesWritten(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = globalObject; - return JSC.JSValue.jsNumber(this.stream.total_in); - } - - pub fn getClosed(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = globalObject; - return JSC.JSValue.jsBoolean(this.closed); - } - - pub fn close(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue { - _ = this; - _ = globalThis; - _ = callframe; - return .undefined; - } - - pub fn getChunkSize(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = globalObject; - return JSC.JSValue.jsNumber(this.chunkSize); - } - - pub fn getFlush(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = globalObject; - return JSC.JSValue.jsNumber(this.stream.flushOp); - } - - pub fn getFinishFlush(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = globalObject; - return JSC.JSValue.jsNumber(this.stream.finishFlushOp); - } - - pub fn getFullFlush(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = globalObject; - return JSC.JSValue.jsNumber(this.stream.fullFlushOp); - } - - pub fn getMaxOutputLength(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = globalObject; - return JSC.JSValue.jsNumber(this.maxOutputLength); - } -}; - -pub const BrotliDecoder = struct { - pub usingnamespace bun.New(@This()); - pub usingnamespace JSC.Codegen.JSBrotliDecoder; - - globalThis: *JSC.JSGlobalObject, - stream: brotli.BrotliReaderArrayList, - chunkSize: c_uint, - maxOutputLength: usize, - mode: u8, - - has_pending_activity: std.atomic.Value(u32) = std.atomic.Value(u32).init(0), - ref_count: u32 = 1, - poll_ref: bun.Async.KeepAlive = .{}, - write_failure: ?JSC.DeferredError = null, - callback_value: JSC.Strong = .{}, - has_called_end: bool = false, - pending_decode_job_count: std.atomic.Value(u32) = std.atomic.Value(u32).init(0), - closed: bool = false, - - input: Queue = Queue.init(bun.default_allocator), - input_lock: bun.Lock = .{}, - - output: std.ArrayListUnmanaged(u8) = .{}, - output_lock: bun.Lock = .{}, - - freelist: FreeList = .{}, - - pub fn hasPendingActivity(this: *BrotliDecoder) bool { - return this.has_pending_activity.load(.monotonic) > 0; - } - - pub fn deinit(this: *BrotliDecoder) void { - this.callback_value.deinit(); - this.freelist.deinit(); - this.output.deinit(bun.default_allocator); - this.stream.brotli.destroyInstance(); - this.input.deinit(); - this.destroy(); - } - - pub fn constructor(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) ?*BrotliDecoder { - globalThis.throw("BrotliDecoder is not constructable", .{}); - return null; - } - - pub fn create(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSC.JSValue { - const arguments = callframe.arguments(4).slice(); - - if (arguments.len < 4) { - globalThis.throwNotEnoughArguments("BrotliDecoder", 4, arguments.len); - return .zero; - } - - const opts = arguments[0]; - const callback = arguments[2]; - const mode = arguments[3].to(u8); - - const chunkSize = globalThis.getInteger(opts, u32, 1024 * 48, .{ .min = 64, .field_name = "chunkSize" }) orelse return .zero; - const maxOutputLength = globalThis.getInteger(opts, usize, 0, .{ .max = std.math.maxInt(u52), .field_name = "maxOutputLength" }) orelse return .zero; - const flush = globalThis.getInteger(opts, u8, 0, .{ .max = 6, .field_name = "flush" }) orelse return .zero; - const finishFlush = globalThis.getInteger(opts, u8, 2, .{ .max = 6, .field_name = "finishFlush" }) orelse return .zero; - const fullFlush = globalThis.getInteger(opts, u8, 1, .{ .max = 6, .field_name = "fullFlush" }) orelse return .zero; - - var this: *BrotliDecoder = BrotliDecoder.new(.{ - .globalThis = globalThis, - .stream = undefined, // &this.output needs to be a stable pointer - .chunkSize = chunkSize, - .maxOutputLength = maxOutputLength, - .mode = mode, - }); - this.stream = brotli.BrotliReaderArrayList.initWithOptions("", &this.output, bun.default_allocator, .{}, @enumFromInt(flush), @enumFromInt(finishFlush), @enumFromInt(fullFlush)) catch { - globalThis.throw("Failed to create BrotliDecoder", .{}); - return .zero; - }; - - if (opts.get(globalThis, "params")) |params| { - inline for (std.meta.fields(bun.brotli.c.BrotliDecoderParameter)) |f| { - if (!params.isObject()) break; - const idx = params.getIndex(globalThis, f.value); - if (!idx.isNumber()) break; - const was_set = this.stream.brotli.setParameter(@enumFromInt(f.value), idx.toU32()); - if (!was_set) { - globalThis.ERR_ZLIB_INITIALIZATION_FAILED("Initialization failed", .{}).throw(); - this.deinit(); - return .zero; - } - } - } - if (globalThis.hasException()) return .zero; - - const out = this.toJS(globalThis); - this.callback_value.set(globalThis, callback); - - return out; - } - - pub fn finalize(this: *BrotliDecoder) void { - this.deinit(); - } - - fn collectOutputValue(this: *BrotliDecoder) JSC.JSValue { - this.output_lock.lock(); - defer this.output_lock.unlock(); - - defer this.output.clearRetainingCapacity(); - return JSC.ArrayBuffer.createBuffer(this.globalThis, this.output.items); - } - - pub fn runFromJSThread(this: *BrotliDecoder) void { - this.poll_ref.unref(this.globalThis.bunVM()); - - defer _ = this.has_pending_activity.fetchSub(1, .monotonic); - this.drainFreelist(); - - _ = this.callback_value.get().?.call( - this.globalThis, - .undefined, - if (this.write_failure != null) - &.{this.write_failure.?.toError(this.globalThis)} - else - &.{ .null, this.collectOutputValue() }, - ) catch |err| this.globalThis.reportActiveExceptionAsUnhandled(err); - } - - fn drainFreelist(this: *BrotliDecoder) void { - this.freelist.drain(); - } - - pub fn transform(this: *BrotliDecoder, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSC.JSValue { - const arguments = callframe.arguments(3); - - if (arguments.len < 3) { - globalThis.throwNotEnoughArguments("BrotliEncoder.decode", 3, arguments.len); - return .zero; - } - - if (this.has_called_end) { - globalThis.throw("BrotliEncoder.decode called after BrotliEncoder.end", .{}); - return .zero; - } - - const input = callframe.argument(0); - const optional_encoding = callframe.argument(1); - const is_last = callframe.argument(2).toBoolean(); - - const input_to_queue = JSC.Node.BlobOrStringOrBuffer.fromJSWithEncodingValueMaybeAsync(globalThis, bun.default_allocator, input, optional_encoding, true) orelse { - globalThis.throwInvalidArgumentType("BrotliEncoder.decode", "input", "Blob, String, or Buffer"); - return .zero; - }; - - _ = this.has_pending_activity.fetchAdd(1, .monotonic); - if (is_last) - this.has_called_end = true; - - var task = DecodeJob.new(.{ - .decoder = this, - .is_async = true, - .vm = this.globalThis.bunVM(), - }); - - { - this.input_lock.lock(); - defer this.input_lock.unlock(); - - // need to protect because no longer on the stack. unprotected in FreeList.deinit - input_to_queue.protect(); - this.input.writeItem(input_to_queue) catch bun.outOfMemory(); - } - this.poll_ref.ref(task.vm); - JSC.WorkPool.schedule(&task.task); - - return .undefined; - } - - pub fn transformSync(this: *BrotliDecoder, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSC.JSValue { - const arguments = callframe.arguments(4); - - if (arguments.len < 3) { - globalThis.throwNotEnoughArguments("BrotliEncoder.decode", 3, arguments.len); - return .zero; - } - - if (this.has_called_end) { - globalThis.throw("BrotliEncoder.decode called after BrotliEncoder.end", .{}); - return .zero; - } - - const input = callframe.argument(0); - const optional_encoding = callframe.argument(1); - const is_last = callframe.argument(2).toBoolean(); - // const optional_flushFlag = arguments.ptr[3]; - - // const old_flushFlag = this.stream.flushOp; - // defer this.stream.flushOp = old_flushFlag; - // blk: { - // if (!optional_flushFlag.isInt32()) break :blk; - // const int = optional_flushFlag.asInt32(); - // if (int < 0) break :blk; - // if (int > 3) break :blk; - // this.stream.flushOp = @enumFromInt(int); - // } - - const input_to_queue = JSC.Node.BlobOrStringOrBuffer.fromJSWithEncodingValueMaybeAsync(globalThis, bun.default_allocator, input, optional_encoding, true) orelse { - globalThis.throwInvalidArgumentType("BrotliEncoder.decode", "input", "Blob, String, or Buffer"); - return .zero; - }; - - _ = this.has_pending_activity.fetchAdd(1, .monotonic); - if (is_last) - this.has_called_end = true; - - var task: DecodeJob = .{ - .decoder = this, - .is_async = false, - .vm = this.globalThis.bunVM(), - }; - - { - this.input_lock.lock(); - defer this.input_lock.unlock(); - - // need to protect because no longer on the stack. unprotected in FreeList.deinit - input_to_queue.protect(); - this.input.writeItem(input_to_queue) catch bun.outOfMemory(); - } - task.run(); - if (!is_last and this.output.items.len == 0) { - return JSC.Buffer.fromBytes(&.{}, bun.default_allocator, .Uint8Array).toNodeBuffer(globalThis); - } - if (this.write_failure != null) { - globalThis.throwValue(this.write_failure.?.toError(globalThis)); - return .zero; - } - return this.collectOutputValue(); - } - - // We can only run one decode job at a time - // But we don't have an idea of a serial dispatch queue - // So instead, we let you enqueue as many times as you want - // and if one is already running, we just don't do anything - const DecodeJob = struct { - task: JSC.WorkPoolTask = .{ .callback = &runTask }, - decoder: *BrotliDecoder, - is_async: bool, - vm: *JSC.VirtualMachine, - - pub usingnamespace bun.New(@This()); - - pub fn runTask(this: *JSC.WorkPoolTask) void { - var job: *DecodeJob = @fieldParentPtr("task", this); - job.run(); - job.destroy(); - } - - pub fn run(this: *DecodeJob) void { - defer { - _ = this.decoder.has_pending_activity.fetchSub(1, .monotonic); - } - - var any = false; - - if (this.decoder.pending_decode_job_count.fetchAdd(1, .monotonic) >= 0) { - const is_last = this.decoder.has_called_end; - while (true) { - this.decoder.input_lock.lock(); - defer this.decoder.input_lock.unlock(); - if (!is_last) break; - const pending = this.decoder.input.readableSlice(0); - - defer { - this.decoder.freelist.append(pending); - } - - var input_list = std.ArrayListUnmanaged(u8){}; - defer input_list.deinit(bun.default_allocator); - - if (pending.len > 1) { - var count: usize = 0; - for (pending) |input| { - count += input.slice().len; - } - - input_list.ensureTotalCapacityPrecise(bun.default_allocator, count) catch bun.outOfMemory(); - - for (pending) |*input| { - input_list.appendSliceAssumeCapacity(input.slice()); - } - } - - { - this.decoder.output_lock.lock(); - defer this.decoder.output_lock.unlock(); - - const input = if (pending.len <= 1) pending[0].slice() else input_list.items; - this.decoder.stream.input = input; - this.decoder.stream.readAll(false) catch { - any = true; - _ = this.decoder.pending_decode_job_count.fetchSub(1, .monotonic); - if (!this.is_async) { - this.decoder.closed = true; - this.decoder.globalThis.throw("BrotliError", .{}); - return; - } - this.decoder.write_failure = JSC.DeferredError.from(.plainerror, .ERR_OPERATION_FAILED, "BrotliError", .{}); // TODO propogate better error - break; - }; - if (this.decoder.output.items.len > this.decoder.maxOutputLength) { - any = true; - _ = this.decoder.pending_decode_job_count.fetchSub(1, .monotonic); - this.decoder.write_failure = JSC.DeferredError.from(.rangeerror, .ERR_BUFFER_TOO_LARGE, "Cannot create a Buffer larger than {d} bytes", .{this.decoder.maxOutputLength}); - break; - } - } - - any = any or pending.len > 0; - - if (this.decoder.pending_decode_job_count.fetchSub(1, .monotonic) == 0) - break; - } - } - - if (this.is_async and any) { - _ = this.decoder.has_pending_activity.fetchAdd(1, .monotonic); - this.vm.enqueueTaskConcurrent(JSC.ConcurrentTask.create(JSC.Task.init(this.decoder))); - } - } - }; - - pub fn reset(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSC.JSValue { - _ = this; - _ = globalThis; - _ = callframe; - return .undefined; - } - - pub fn getBytesWritten(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = globalObject; - return JSC.JSValue.jsNumber(this.stream.total_in); - } - - pub fn getClosed(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = globalObject; - return JSC.JSValue.jsBoolean(this.closed); - } - - pub fn close(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue { - _ = this; - _ = globalThis; - _ = callframe; - return .undefined; - } - - pub fn getChunkSize(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = globalObject; - return JSC.JSValue.jsNumber(this.chunkSize); - } - - pub fn getFlush(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = globalObject; - return JSC.JSValue.jsNumber(this.stream.flushOp); - } - - pub fn getFinishFlush(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = globalObject; - return JSC.JSValue.jsNumber(this.stream.finishFlushOp); - } - - pub fn getFullFlush(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = globalObject; - return JSC.JSValue.jsNumber(this.stream.fullFlushOp); - } - - pub fn getMaxOutputLength(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = globalObject; - return JSC.JSValue.jsNumber(this.maxOutputLength); - } -}; diff --git a/src/bun.js/api/js_zlib.zig b/src/bun.js/api/js_zlib.zig deleted file mode 100644 index 7b2d812e63..0000000000 --- a/src/bun.js/api/js_zlib.zig +++ /dev/null @@ -1,1014 +0,0 @@ -const std = @import("std"); -const bun = @import("root").bun; -const Environment = bun.Environment; -const JSC = bun.JSC; -const string = bun.string; -const Output = bun.Output; -const ZigString = JSC.ZigString; -const Queue = std.fifo.LinearFifo(JSC.Node.BlobOrStringOrBuffer, .Dynamic); - -pub const ZlibEncoder = struct { - pub usingnamespace bun.New(@This()); - pub usingnamespace JSC.Codegen.JSZlibEncoder; - - globalThis: *JSC.JSGlobalObject, - stream: bun.zlib.ZlibCompressorStreaming, - maxOutputLength: usize, - - freelist: Queue = Queue.init(bun.default_allocator), - freelist_write_lock: bun.Lock = .{}, - - input: Queue = Queue.init(bun.default_allocator), - input_lock: bun.Lock = .{}, - - has_called_end: bool = false, - callback_value: JSC.Strong = .{}, - - output: std.ArrayListUnmanaged(u8) = .{}, - output_lock: bun.Lock = .{}, - - has_pending_activity: std.atomic.Value(u32) = std.atomic.Value(u32).init(0), - pending_encode_job_count: std.atomic.Value(u32) = std.atomic.Value(u32).init(0), - ref_count: u32 = 1, - write_failure: ?JSC.DeferredError = null, - poll_ref: bun.Async.KeepAlive = .{}, - closed: bool = false, - - pub fn constructor(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) ?*@This() { - _ = callframe; - globalThis.throw("ZlibEncoder is not constructable", .{}); - return null; - } - - pub fn create(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue { - const arguments = callframe.arguments(4).slice(); - - if (arguments.len < 4) { - globalThis.throwNotEnoughArguments("ZlibEncoder", 4, arguments.len); - return .zero; - } - - const opts = arguments[0]; - const callback = arguments[2]; - const mode = arguments[3].to(bun.zlib.NodeMode); - - var options = Options.fromJS(globalThis, mode, opts) orelse return .zero; - - if (mode == .GZIP or mode == .GUNZIP) options.windowBits += 16; - if (mode == .UNZIP) options.windowBits += 32; - if (mode == .DEFLATERAW or mode == .INFLATERAW) options.windowBits *= -1; - - // In zlib v1.2.9, 8 become an invalid value for this parameter, so we gracefully fix it. - // Ref: https://github.com/nodejs/node/commit/241eb6122ee6f36de16ee4ed4a6a291510b1807f - if (mode == .DEFLATERAW and options.windowBits == -8) options.windowBits = -9; - - var this: *ZlibEncoder = ZlibEncoder.new(.{ - .globalThis = globalThis, - .maxOutputLength = options.maxOutputLength, - .stream = .{ - .mode = mode, - .chunkSize = options.chunkSize, - .flush = @enumFromInt(options.flush), - .finishFlush = @enumFromInt(options.finishFlush), - .fullFlush = @enumFromInt(options.fullFlush), - .level = options.level, - .windowBits = options.windowBits, - .memLevel = options.memLevel, - .strategy = options.strategy, - .dictionary = options.dictionary.slice(), - }, - }); - this.stream.init() catch { - globalThis.throw("Failed to create ZlibEncoder", .{}); - return .zero; - }; - - const out = this.toJS(globalThis); - this.callback_value.set(globalThis, callback); - - return out; - } - - pub fn finalize(this: *@This()) callconv(.C) void { - this.deinit(); - } - - pub fn deinit(this: *@This()) void { - this.input.deinit(); - this.output.deinit(bun.default_allocator); - this.callback_value.deinit(); - this.destroy(); - } - - pub fn transformSync(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue { - const arguments = callframe.arguments(4); - - if (arguments.len < 3) { - globalThis.throwNotEnoughArguments("ZlibEncoder.encode", 3, arguments.len); - return .zero; - } - - if (this.has_called_end) { - globalThis.throw("ZlibEncoder.encodeSync called after ZlibEncoder.end", .{}); - return .zero; - } - - const input = callframe.argument(0); - const optional_encoding = callframe.argument(1); - const is_last = callframe.argument(2).toBoolean(); - const optional_flushFlag = arguments.ptr[3]; - - const old_flushFlag = this.stream.flush; - defer this.stream.flush = old_flushFlag; - blk: { - if (!optional_flushFlag.isInt32()) break :blk; - const int = optional_flushFlag.asInt32(); - if (int < 0) break :blk; - if (int > 5) break :blk; - this.stream.flush = @enumFromInt(int); - } - - const input_to_queue = JSC.Node.BlobOrStringOrBuffer.fromJSWithEncodingValueMaybeAsync(globalThis, bun.default_allocator, input, optional_encoding, false) orelse { - return globalThis.throwInvalidArgumentTypeValue("buffer", "string or an instance of Buffer, TypedArray, DataView, or ArrayBuffer", input); - }; - defer input_to_queue.deinit(); - - if (is_last) - this.has_called_end = true; - - { - this.stream.write(input_to_queue.slice(), &this.output, true) catch |err| return handleTransformSyncStreamError(err, globalThis, this.stream.err_msg, &this.closed); - } - if (this.output.items.len > this.maxOutputLength) { - globalThis.ERR_BUFFER_TOO_LARGE("Cannot create a Buffer larger than {d} bytes", .{this.maxOutputLength}).throw(); - return .zero; - } - if (is_last) { - this.stream.end(&this.output) catch |err| return handleTransformSyncStreamError(err, globalThis, this.stream.err_msg, &this.closed); - } - if (this.output.items.len > this.maxOutputLength) { - globalThis.ERR_BUFFER_TOO_LARGE("Cannot create a Buffer larger than {d} bytes", .{this.maxOutputLength}).throw(); - return .zero; - } - - if (!is_last and this.output.items.len == 0) { - return JSC.Buffer.fromBytes(&.{}, bun.default_allocator, .Uint8Array).toNodeBuffer(globalThis); - } - if (this.write_failure != null) { - globalThis.vm().throwError(globalThis, this.write_failure.?.toError(globalThis)); - return .zero; - } - return this.collectOutputValue(); - } - - pub fn transformWith(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue { - const arguments = callframe.arguments(4); - - if (arguments.len < 4) { - globalThis.throwNotEnoughArguments("ZlibEncoder.encode", 4, arguments.len); - return .zero; - } - - if (this.has_called_end) { - globalThis.throw("ZlibEncoder.encodeSync called after ZlibEncoder.end", .{}); - return .zero; - } - - const input = callframe.argument(0); - const optional_encoding = callframe.argument(1); - const thisctx = arguments.ptr[2]; - const is_last = callframe.argument(3).toBoolean(); - - const push_fn: JSC.JSValue = thisctx.get(globalThis, "push") orelse { - globalThis.throw("are you sure this is a stream.Transform?", .{}); - return .zero; - }; - if (globalThis.hasException()) return .zero; - - const input_to_queue = JSC.Node.BlobOrStringOrBuffer.fromJSWithEncodingValueMaybeAsync(globalThis, bun.default_allocator, input, optional_encoding, false) orelse { - return globalThis.throwInvalidArgumentTypeValue("buffer", "string or an instance of Buffer, TypedArray, DataView, or ArrayBuffer", input); - }; - defer input_to_queue.deinit(); - - if (is_last) - this.has_called_end = true; - - const err_buffer_too_large = globalThis.ERR_BUFFER_TOO_LARGE("Cannot create a Buffer larger than {d} bytes", .{this.maxOutputLength}); - - { - this.stream.write(input_to_queue.slice(), &this.output, false) catch |err| return handleTransformSyncStreamError(err, globalThis, this.stream.err_msg, &this.closed); - if (this.output.items.len > this.maxOutputLength) { - err_buffer_too_large.throw(); - return .zero; - } - while (true) { - const done = this.stream.doWork(&this.output, this.stream.flush) catch |err| return handleTransformSyncStreamError(err, globalThis, this.stream.err_msg, &this.closed); - if (this.output.items.len > this.maxOutputLength) { - err_buffer_too_large.throw(); - return .zero; - } - if (this.output.items.len > 0) _ = push_fn.call(globalThis, thisctx, &.{this.collectOutputValue()}) catch return .zero; - if (done) break; - } - } - if (is_last) { - this.stream.end(&this.output) catch |err| return handleTransformSyncStreamError(err, globalThis, this.stream.err_msg, &this.closed); - if (this.output.items.len > this.maxOutputLength) { - globalThis.ERR_BUFFER_TOO_LARGE("Cannot create a Buffer larger than {d} bytes", .{this.maxOutputLength}).throw(); - return .zero; - } - if (this.output.items.len > 0) _ = push_fn.call(globalThis, thisctx, &.{this.collectOutputValue()}) catch return .zero; - } - return .undefined; - } - - pub fn transform(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue { - const arguments = callframe.arguments(3); - - if (arguments.len < 3) { - globalThis.throwNotEnoughArguments("ZlibEncoder.encode", 3, arguments.len); - return .zero; - } - - if (this.has_called_end) { - globalThis.throw("ZlibEncoder.encode called after ZlibEncoder.end", .{}); - return .zero; - } - - const input = callframe.argument(0); - const optional_encoding = callframe.argument(1); - const is_last = callframe.argument(2).toBoolean(); - - const input_to_queue = JSC.Node.BlobOrStringOrBuffer.fromJSWithEncodingValueMaybeAsync(globalThis, bun.default_allocator, input, optional_encoding, true) orelse { - globalThis.throwInvalidArgumentType("ZlibEncoder.encode", "input", "Blob, String, or Buffer"); - return .zero; - }; - - _ = this.has_pending_activity.fetchAdd(1, .monotonic); - if (is_last) - this.has_called_end = true; - - var task = EncodeJob.new(.{ - .encoder = this, - }); - - { - this.input_lock.lock(); - defer this.input_lock.unlock(); - - this.input.writeItem(input_to_queue) catch unreachable; - } - this.poll_ref.ref(globalThis.bunVM()); - JSC.WorkPool.schedule(&task.task); - - return .undefined; - } - - pub fn runFromJSThread(this: *@This()) void { - this.poll_ref.unref(this.globalThis.bunVM()); - - defer _ = this.has_pending_activity.fetchSub(1, .monotonic); - this.drainFreelist(); - - _ = this.callback_value.get().?.call( - this.globalThis, - .undefined, - if (this.write_failure != null) - &.{this.write_failure.?.toError(this.globalThis)} - else - &.{ .null, this.collectOutputValue() }, - ) catch |err| this.globalThis.reportActiveExceptionAsUnhandled(err); - } - - pub fn hasPendingActivity(this: *@This()) callconv(.C) bool { - return this.has_pending_activity.load(.monotonic) > 0; - } - - fn drainFreelist(this: *ZlibEncoder) void { - this.freelist_write_lock.lock(); - defer this.freelist_write_lock.unlock(); - const to_free = this.freelist.readableSlice(0); - for (to_free) |*input| { - input.deinit(); - } - this.freelist.discard(to_free.len); - } - - fn collectOutputValue(this: *ZlibEncoder) JSC.JSValue { - this.output_lock.lock(); - defer this.output_lock.unlock(); - - defer this.output.clearRetainingCapacity(); - return JSC.ArrayBuffer.createBuffer(this.globalThis, this.output.items); - } - - const EncodeJob = struct { - task: JSC.WorkPoolTask = .{ .callback = &runTask }, - encoder: *ZlibEncoder, - - pub usingnamespace bun.New(@This()); - - pub fn runTask(this: *JSC.WorkPoolTask) void { - var job: *EncodeJob = @fieldParentPtr("task", this); - job.run(); - job.destroy(); - } - - pub fn run(this: *EncodeJob) void { - const vm = this.encoder.globalThis.bunVMConcurrently(); - defer this.encoder.poll_ref.unrefConcurrently(vm); - defer { - _ = this.encoder.has_pending_activity.fetchSub(1, .monotonic); - } - - var any = false; - - if (this.encoder.pending_encode_job_count.fetchAdd(1, .monotonic) >= 0) { - const is_last = this.encoder.has_called_end; - outer: while (true) { - this.encoder.input_lock.lock(); - defer this.encoder.input_lock.unlock(); - const readable = this.encoder.input.readableSlice(0); - defer this.encoder.input.discard(readable.len); - const pending = readable; - - defer { - this.encoder.freelist_write_lock.lock(); - this.encoder.freelist.write(pending) catch unreachable; - this.encoder.freelist_write_lock.unlock(); - } - for (pending) |input| { - const output = &this.encoder.output; - this.encoder.stream.write(input.slice(), output, true) catch |e| { - any = true; - _ = this.encoder.pending_encode_job_count.fetchSub(1, .monotonic); - this.encoder.write_failure = JSC.DeferredError.from(.plainerror, .ERR_OPERATION_FAILED, "ZlibError: {s}", .{@errorName(e)}); // TODO propagate better error - break :outer; - }; - if (this.encoder.output.items.len > this.encoder.maxOutputLength) { - any = true; - _ = this.encoder.pending_encode_job_count.fetchSub(1, .monotonic); - this.encoder.write_failure = JSC.DeferredError.from(.rangeerror, .ERR_BUFFER_TOO_LARGE, "Cannot create a Buffer larger than {d} bytes", .{this.encoder.maxOutputLength}); - break :outer; - } - } - - any = any or pending.len > 0; - - if (this.encoder.pending_encode_job_count.fetchSub(1, .monotonic) == 0) - break; - } - - if (is_last and any) { - const output = &this.encoder.output; - this.encoder.output_lock.lock(); - defer this.encoder.output_lock.unlock(); - - this.encoder.stream.end(output) catch |e| { - _ = this.encoder.pending_encode_job_count.fetchSub(1, .monotonic); - this.encoder.write_failure = JSC.DeferredError.from(.plainerror, .ERR_OPERATION_FAILED, "ZlibError: {s}", .{@errorName(e)}); // TODO propogate better error - return; - }; - if (this.encoder.output.items.len > this.encoder.maxOutputLength) { - _ = this.encoder.pending_encode_job_count.fetchSub(1, .monotonic); - this.encoder.write_failure = JSC.DeferredError.from(.rangeerror, .ERR_BUFFER_TOO_LARGE, "Cannot create a Buffer larger than {d} bytes", .{this.encoder.maxOutputLength}); - return; - } - } - } - - if (any) { - _ = this.encoder.has_pending_activity.fetchAdd(1, .monotonic); - this.encoder.poll_ref.refConcurrently(vm); - this.encoder.poll_ref.refConcurrently(vm); - vm.enqueueTaskConcurrent(JSC.ConcurrentTask.create(JSC.Task.init(this.encoder))); - } - } - }; - - pub fn reset(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue { - _ = globalThis; - _ = callframe; - _ = bun.zlib.deflateReset(&this.stream.state); - return .undefined; - } - - pub fn getBytesWritten(this: *@This(), globalObject: *JSC.JSGlobalObject) callconv(.C) JSC.JSValue { - _ = globalObject; - return JSC.JSValue.jsNumber(@as(u64, this.stream.state.total_in)); - } - - pub fn getLevel(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = globalObject; - return JSC.JSValue.jsNumber(this.stream.level); - } - - pub fn getStrategy(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = globalObject; - return JSC.JSValue.jsNumber(this.stream.strategy); - } - - pub fn getClosed(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = globalObject; - return JSC.JSValue.jsBoolean(this.closed); - } - - pub fn close(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue { - _ = this; - _ = globalThis; - _ = callframe; - return .undefined; - } - - pub fn params(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue { - const arguments = callframe.arguments(3).ptr; - if (this.stream.mode != .DEFLATE) return .undefined; - - const level = if (arguments[0] != .zero) (globalThis.validateIntegerRange(arguments[0], i16, -1, .{ .max = 9, .min = -1, .field_name = "level" }) orelse return .zero) else this.stream.level; - const strategy = if (arguments[1] != .zero) (globalThis.validateIntegerRange(arguments[1], u8, 0, .{ .max = 4, .min = 0, .field_name = "strategy" }) orelse return .zero) else this.stream.strategy; - this.stream.params(level, strategy); - - if (arguments[2] != .zero) { - if (!arguments[2].isFunction()) { - return globalThis.throwInvalidArgumentTypeValue("callback", "function", arguments[2]); - } - this.callback_value.set(globalThis, arguments[2]); - } - - return .undefined; - } - - pub fn getChunkSize(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = globalObject; - return JSC.JSValue.jsNumber(this.stream.chunkSize); - } - - pub fn getFlush(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = globalObject; - return JSC.JSValue.jsNumber(this.stream.flush); - } - - pub fn getFinishFlush(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = globalObject; - return JSC.JSValue.jsNumber(this.stream.finishFlush); - } - - pub fn getFullFlush(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = globalObject; - return JSC.JSValue.jsNumber(this.stream.fullFlush); - } - - pub fn getMaxOutputLength(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = globalObject; - return JSC.JSValue.jsNumber(this.maxOutputLength); - } -}; - -pub const ZlibDecoder = struct { - pub usingnamespace bun.New(@This()); - pub usingnamespace JSC.Codegen.JSZlibDecoder; - - globalThis: *JSC.JSGlobalObject, - stream: bun.zlib.ZlibDecompressorStreaming, - maxOutputLength: usize, - - freelist: Queue = Queue.init(bun.default_allocator), - freelist_write_lock: bun.Lock = .{}, - - input: Queue = Queue.init(bun.default_allocator), - input_lock: bun.Lock = .{}, - - has_called_end: bool = false, - callback_value: JSC.Strong = .{}, - - output: std.ArrayListUnmanaged(u8) = .{}, - output_lock: bun.Lock = .{}, - - has_pending_activity: std.atomic.Value(u32) = std.atomic.Value(u32).init(0), - pending_encode_job_count: std.atomic.Value(u32) = std.atomic.Value(u32).init(0), - ref_count: u32 = 1, - write_failure: ?JSC.DeferredError = null, - poll_ref: bun.Async.KeepAlive = .{}, - closed: bool = false, - - pub fn constructor(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) ?*@This() { - _ = callframe; - globalThis.throw("ZlibDecoder is not constructable", .{}); - return null; - } - - pub fn create(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue { - const arguments = callframe.arguments(4).slice(); - - if (arguments.len < 4) { - globalThis.throwNotEnoughArguments("ZlibDecoder", 4, arguments.len); - return .zero; - } - - const opts = arguments[0]; - const callback = arguments[2]; - const mode = arguments[3].to(bun.zlib.NodeMode); - - var options = Options.fromJS(globalThis, mode, opts) orelse return .zero; - - if (mode == .GZIP or mode == .GUNZIP) options.windowBits += 16; - if (mode == .UNZIP) options.windowBits += 32; - if (mode == .DEFLATERAW or mode == .INFLATERAW) options.windowBits *= -1; - - var this: *ZlibDecoder = ZlibDecoder.new(.{ - .globalThis = globalThis, - .maxOutputLength = options.maxOutputLength, - .stream = .{ - .mode = mode, - .chunkSize = options.chunkSize, - .flush = @enumFromInt(options.flush), - .finishFlush = @enumFromInt(options.finishFlush), - .fullFlush = @enumFromInt(options.fullFlush), - .windowBits = options.windowBits, - .dictionary = options.dictionary.slice(), - }, - }); - this.stream.init() catch { - globalThis.throw("Failed to create ZlibDecoder", .{}); - return .zero; - }; - - const out = this.toJS(globalThis); - this.callback_value.set(globalThis, callback); - - return out; - } - - pub fn finalize(this: *@This()) callconv(.C) void { - this.deinit(); - } - - pub fn deinit(this: *@This()) void { - this.input.deinit(); - this.output.deinit(bun.default_allocator); - this.callback_value.deinit(); - this.destroy(); - } - - pub fn transformSync(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue { - const arguments = callframe.arguments(4); - - if (arguments.len < 3) { - globalThis.throwNotEnoughArguments("ZlibDecoder.encode", 3, arguments.len); - return .zero; - } - - if (this.has_called_end) { - globalThis.throw("ZlibDecoder.encodeSync called after ZlibDecoder.end", .{}); - return .zero; - } - - const input = callframe.argument(0); - const optional_encoding = callframe.argument(1); - const is_last = callframe.argument(2).toBoolean(); - const optional_flushFlag = arguments.ptr[3]; - - const old_flushFlag = this.stream.flush; - defer this.stream.flush = old_flushFlag; - blk: { - if (!optional_flushFlag.isInt32()) break :blk; - const int = optional_flushFlag.asInt32(); - if (int < 0) break :blk; - if (int > 5) break :blk; - this.stream.flush = @enumFromInt(int); - } - - const input_to_queue = JSC.Node.BlobOrStringOrBuffer.fromJSWithEncodingValueMaybeAsync(globalThis, bun.default_allocator, input, optional_encoding, true) orelse { - return globalThis.throwInvalidArgumentTypeValue("buffer", "string or an instance of Buffer, TypedArray, DataView, or ArrayBuffer", input); - }; - - if (is_last) - this.has_called_end = true; - - { - this.stream.writeAll(input_to_queue.slice(), &this.output, true) catch |err| return handleTransformSyncStreamError(err, globalThis, this.stream.err_msg, &this.closed); - } - if (this.output.items.len > this.maxOutputLength) { - globalThis.ERR_BUFFER_TOO_LARGE("Cannot create a Buffer larger than {d} bytes", .{this.maxOutputLength}).throw(); - return .zero; - } - if (is_last) { - this.stream.end(&this.output) catch |err| return handleTransformSyncStreamError(err, globalThis, this.stream.err_msg, &this.closed); - } - if (this.output.items.len > this.maxOutputLength) { - globalThis.ERR_BUFFER_TOO_LARGE("Cannot create a Buffer larger than {d} bytes", .{this.maxOutputLength}).throw(); - return .zero; - } - - if (!is_last and this.output.items.len == 0) { - return JSC.Buffer.fromBytes(&.{}, bun.default_allocator, .Uint8Array).toNodeBuffer(globalThis); - } - if (this.write_failure != null) { - globalThis.vm().throwError(globalThis, this.write_failure.?.toError(globalThis)); - return .zero; - } - return this.collectOutputValue(); - } - - pub fn transformWith(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue { - const arguments = callframe.arguments(4); - - if (arguments.len < 4) { - globalThis.throwNotEnoughArguments("ZlibEncoder.encode", 4, arguments.len); - return .zero; - } - - if (this.has_called_end) { - globalThis.throw("ZlibEncoder.encodeSync called after ZlibEncoder.end", .{}); - return .zero; - } - - const input = callframe.argument(0); - const optional_encoding = callframe.argument(1); - const thisctx = arguments.ptr[2]; - const is_last = callframe.argument(3).toBoolean(); - - const push_fn: JSC.JSValue = thisctx.get(globalThis, "push") orelse { - globalThis.throw("are you sure this is a stream.Transform?", .{}); - return .zero; - }; - if (globalThis.hasException()) return .zero; - - const input_to_queue = JSC.Node.BlobOrStringOrBuffer.fromJSWithEncodingValue(globalThis, bun.default_allocator, input, optional_encoding) orelse { - return globalThis.throwInvalidArgumentTypeValue("buffer", "string or an instance of Buffer, TypedArray, DataView, or ArrayBuffer", input); - }; - defer input_to_queue.deinit(); - if (is_last) - this.has_called_end = true; - - const err_buffer_too_large = globalThis.ERR_BUFFER_TOO_LARGE("Cannot create a Buffer larger than {d} bytes", .{this.maxOutputLength}); - - { - const input_slice = input_to_queue.slice(); - this.stream.writeAll(input_slice, &this.output, false) catch |err| return handleTransformSyncStreamError(err, globalThis, this.stream.err_msg, &this.closed); - if (this.output.items.len > this.maxOutputLength) { - err_buffer_too_large.throw(); - return .zero; - } - while (this.stream.do_inflate_loop) { - const done = this.stream.doWork(&this.output, this.stream.flush) catch |err| return handleTransformSyncStreamError(err, globalThis, this.stream.err_msg, &this.closed); - if (this.output.items.len > this.maxOutputLength) { - err_buffer_too_large.throw(); - return .zero; - } - if (this.output.items.len > 0) _ = push_fn.call(globalThis, thisctx, &.{this.collectOutputValue()}) catch return .zero; - if (done) break; - } - } - if (is_last) { - this.stream.end(&this.output) catch |err| return handleTransformSyncStreamError(err, globalThis, this.stream.err_msg, &this.closed); - if (this.output.items.len > this.maxOutputLength) { - globalThis.ERR_BUFFER_TOO_LARGE("Cannot create a Buffer larger than {d} bytes", .{this.maxOutputLength}).throw(); - return .zero; - } - if (this.output.items.len > 0) _ = push_fn.call(globalThis, thisctx, &.{this.collectOutputValue()}) catch return .zero; - } - return .undefined; - } - - pub fn transform(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue { - const arguments = callframe.arguments(3); - - if (arguments.len < 3) { - globalThis.throwNotEnoughArguments("ZlibDecoder.encode", 3, arguments.len); - return .zero; - } - - if (this.has_called_end) { - globalThis.throw("ZlibDecoder.encode called after ZlibDecoder.end", .{}); - return .zero; - } - - const input = callframe.argument(0); - const optional_encoding = callframe.argument(1); - const is_last = callframe.argument(2).toBoolean(); - - const input_to_queue = JSC.Node.BlobOrStringOrBuffer.fromJSWithEncodingValueMaybeAsync(globalThis, bun.default_allocator, input, optional_encoding, true) orelse { - globalThis.throwInvalidArgumentType("ZlibDecoder.encode", "input", "Blob, String, or Buffer"); - return .zero; - }; - - _ = this.has_pending_activity.fetchAdd(1, .monotonic); - if (is_last) - this.has_called_end = true; - - var task = DecodeJob.new(.{ - .decoder = this, - }); - - { - this.input_lock.lock(); - defer this.input_lock.unlock(); - - this.input.writeItem(input_to_queue) catch unreachable; - } - this.poll_ref.ref(globalThis.bunVM()); - JSC.WorkPool.schedule(&task.task); - - return .undefined; - } - - pub fn runFromJSThread(this: *@This()) void { - this.poll_ref.unref(this.globalThis.bunVM()); - - defer _ = this.has_pending_activity.fetchSub(1, .monotonic); - this.drainFreelist(); - - _ = this.callback_value.get().?.call( - this.globalThis, - .undefined, - if (this.write_failure != null) - &.{this.write_failure.?.toError(this.globalThis)} - else - &.{ .null, this.collectOutputValue() }, - ) catch |err| this.globalThis.reportActiveExceptionAsUnhandled(err); - } - - pub fn hasPendingActivity(this: *@This()) callconv(.C) bool { - return this.has_pending_activity.load(.monotonic) > 0; - } - - fn drainFreelist(this: *ZlibDecoder) void { - this.freelist_write_lock.lock(); - defer this.freelist_write_lock.unlock(); - const to_free = this.freelist.readableSlice(0); - for (to_free) |*input| { - input.deinit(); - } - this.freelist.discard(to_free.len); - } - - fn collectOutputValue(this: *ZlibDecoder) JSC.JSValue { - this.output_lock.lock(); - defer this.output_lock.unlock(); - - defer this.output.clearRetainingCapacity(); - return JSC.ArrayBuffer.createBuffer(this.globalThis, this.output.items); - } - - const DecodeJob = struct { - task: JSC.WorkPoolTask = .{ .callback = &runTask }, - decoder: *ZlibDecoder, - - pub usingnamespace bun.New(@This()); - - pub fn runTask(this: *JSC.WorkPoolTask) void { - var job: *DecodeJob = @fieldParentPtr("task", this); - job.run(); - job.destroy(); - } - - pub fn run(this: *DecodeJob) void { - const vm = this.decoder.globalThis.bunVMConcurrently(); - defer this.decoder.poll_ref.unrefConcurrently(vm); - defer { - _ = this.decoder.has_pending_activity.fetchSub(1, .monotonic); - } - - var any = false; - - if (this.decoder.pending_encode_job_count.fetchAdd(1, .monotonic) >= 0) outer: { - const is_last = this.decoder.has_called_end; - while (true) { - this.decoder.input_lock.lock(); - defer this.decoder.input_lock.unlock(); - const readable = this.decoder.input.readableSlice(0); - defer this.decoder.input.discard(readable.len); - const pending = readable; - - defer { - this.decoder.freelist_write_lock.lock(); - this.decoder.freelist.write(pending) catch unreachable; - this.decoder.freelist_write_lock.unlock(); - } - for (pending) |input| { - const output = &this.decoder.output; - this.decoder.stream.writeAll(input.slice(), output, true) catch |e| { - any = true; - _ = this.decoder.pending_encode_job_count.fetchSub(1, .monotonic); - switch (e) { - error.ZlibError => { - const message = std.mem.sliceTo(this.decoder.stream.err_msg.?, 0); - this.decoder.write_failure = JSC.DeferredError.from(.plainerror, .ERR_OPERATION_FAILED, "{s}", .{message}); - break :outer; - }, - else => {}, - } - this.decoder.write_failure = JSC.DeferredError.from(.plainerror, .ERR_OPERATION_FAILED, "ZlibError: {s}", .{@errorName(e)}); // TODO propogate better error - break :outer; - }; - } - - any = any or pending.len > 0; - - if (this.decoder.pending_encode_job_count.fetchSub(1, .monotonic) == 0) - break; - } - - if (is_last and any) { - const output = &this.decoder.output; - this.decoder.output_lock.lock(); - defer this.decoder.output_lock.unlock(); - - this.decoder.stream.end(output) catch |e| { - any = true; - _ = this.decoder.pending_encode_job_count.fetchSub(1, .monotonic); - switch (e) { - error.ZlibError => { - const message = std.mem.sliceTo(this.decoder.stream.err_msg.?, 0); - this.decoder.write_failure = JSC.DeferredError.from(.plainerror, .ERR_OPERATION_FAILED, "{s}", .{message}); - break :outer; - }, - else => { - this.decoder.write_failure = JSC.DeferredError.from(.plainerror, .ERR_OPERATION_FAILED, "ZlibError: {s}", .{@errorName(e)}); // TODO propogate better error - break :outer; - }, - } - }; - if (output.items.len > this.decoder.maxOutputLength) { - any = true; - _ = this.decoder.pending_encode_job_count.fetchSub(1, .monotonic); - this.decoder.write_failure = JSC.DeferredError.from(.rangeerror, .ERR_BUFFER_TOO_LARGE, "Cannot create a Buffer larger than {d} bytes", .{this.decoder.maxOutputLength}); - break :outer; - } - } - } - - if (any) { - _ = this.decoder.has_pending_activity.fetchAdd(1, .monotonic); - this.decoder.poll_ref.refConcurrently(vm); - this.decoder.poll_ref.refConcurrently(vm); - vm.enqueueTaskConcurrent(JSC.ConcurrentTask.create(JSC.Task.init(this.decoder))); - } - } - }; - - pub fn reset(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue { - _ = globalThis; - _ = callframe; - _ = bun.zlib.inflateReset(&this.stream.state); - return .undefined; - } - - pub fn getBytesWritten(this: *@This(), globalObject: *JSC.JSGlobalObject) callconv(.C) JSC.JSValue { - _ = globalObject; - return JSC.JSValue.jsNumber(@as(u64, this.stream.state.total_in)); - } - - pub fn getLevel(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = this; - _ = globalObject; - return JSC.JSValue.jsUndefined(); - } - - pub fn getStrategy(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = this; - _ = globalObject; - return JSC.JSValue.jsUndefined(); - } - - pub fn getClosed(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = globalObject; - return JSC.JSValue.jsBoolean(this.closed); - } - - pub fn close(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue { - _ = this; - _ = globalThis; - _ = callframe; - return .undefined; - } - - pub fn params(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue { - _ = this; - _ = globalThis; - _ = callframe; - return .undefined; - } - - pub fn getChunkSize(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = globalObject; - return JSC.JSValue.jsNumber(this.stream.chunkSize); - } - - pub fn getFlush(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = globalObject; - return JSC.JSValue.jsNumber(this.stream.flush); - } - - pub fn getFinishFlush(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = globalObject; - return JSC.JSValue.jsNumber(this.stream.finishFlush); - } - - pub fn getFullFlush(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = globalObject; - return JSC.JSValue.jsNumber(this.stream.fullFlush); - } - - pub fn getMaxOutputLength(this: *@This(), globalObject: *JSC.JSGlobalObject) JSC.JSValue { - _ = globalObject; - return JSC.JSValue.jsNumber(this.maxOutputLength); - } -}; - -const Options = struct { - chunkSize: c_uint, - level: c_int, - windowBits: c_int, - memLevel: c_int, - strategy: c_int, - dictionary: JSC.Buffer, - maxOutputLength: usize, - flush: u8, - finishFlush: u8, - fullFlush: u8, - - pub fn fromJS(globalThis: *JSC.JSGlobalObject, mode: bun.zlib.NodeMode, opts: JSC.JSValue) ?Options { - const chunkSize = globalThis.getInteger(opts, c_uint, 48 * 1024, .{ - .field_name = "chunkSize", - .min = 64, - }) orelse return null; - const level = globalThis.getInteger(opts, i16, -1, .{ .field_name = "level", .min = -1, .max = 9 }) orelse return null; - const memLevel = globalThis.getInteger(opts, u8, 8, .{ .field_name = "memLevel", .min = 8, .max = 255 }) orelse return null; - const strategy = globalThis.getInteger(opts, u8, 0, .{ .field_name = "strategy", .min = 0, .max = 4 }) orelse return null; - const maxOutputLength = globalThis.getInteger(opts, usize, std.math.maxInt(u52), .{ .field_name = "maxOutputLength", .min = 0, .max = std.math.maxInt(u52) }) orelse return null; - const flush = globalThis.getInteger(opts, u8, 0, .{ .field_name = "flush", .min = 0, .max = 5 }) orelse return null; - const finishFlush = globalThis.getInteger(opts, u8, 4, .{ .field_name = "finishFlush", .min = 0, .max = 5 }) orelse return null; - const fullFlush = globalThis.getInteger(opts, u8, 3, .{ .field_name = "fullFlush", .min = 0, .max = 5 }) orelse return null; - - const windowBits = switch (mode) { - .NONE, - .BROTLI_DECODE, - .BROTLI_ENCODE, - => unreachable, - .DEFLATE, .DEFLATERAW => globalThis.getInteger(opts, u8, 15, .{ .min = 8, .max = 15, .field_name = "windowBits" }) orelse return null, - .INFLATE, .INFLATERAW => getWindowBits(globalThis, opts, "windowBits", u8, 8, 15, 15) orelse return null, - .GZIP => globalThis.getInteger(opts, i16, 15, .{ .min = 9, .max = 15, .field_name = "windowBits" }) orelse return null, - .GUNZIP, .UNZIP => getWindowBits(globalThis, opts, "windowBits", i16, 9, 15, 15) orelse return null, - }; - - const dictionary = blk: { - var exceptionref: JSC.C.JSValueRef = null; - const value: JSC.JSValue = opts.get(globalThis, "dictionary") orelse { - if (globalThis.hasException()) return null; - break :blk JSC.Buffer.fromBytes(&.{}, bun.default_allocator, .Uint8Array); - }; - if (value.isUndefined()) { - break :blk JSC.Buffer.fromBytes(&.{}, bun.default_allocator, .Uint8Array); - } - const buffer = JSC.Buffer.fromJS(globalThis, value, &exceptionref) orelse { - const ty_str = value.jsTypeString(globalThis).toSlice(globalThis, bun.default_allocator); - defer ty_str.deinit(); - globalThis.ERR_INVALID_ARG_TYPE("The \"options.dictionary\" property must be an instance of Buffer, TypedArray, DataView, or ArrayBuffer. Received {s}", .{ty_str.slice()}).throw(); - return null; - }; - if (exceptionref) |ptr| { - globalThis.throwValue(JSC.JSValue.c(ptr)); - return null; - } - break :blk buffer; - }; - - return .{ - .chunkSize = chunkSize, - .level = level, - .windowBits = windowBits, - .memLevel = memLevel, - .strategy = strategy, - .dictionary = dictionary, - .maxOutputLength = maxOutputLength, - .flush = flush, - .finishFlush = finishFlush, - .fullFlush = fullFlush, - }; - } - - // Specialization of globalThis.checkRangesOrGetDefault since windowBits also allows 0 when decompressing - fn getWindowBits(this: *JSC.JSGlobalObject, obj: JSC.JSValue, comptime field_name: []const u8, comptime T: type, comptime min: T, comptime max: T, comptime default: T) ?T { - return this.getInteger(obj, T, default, .{ - .field_name = field_name, - .min = min, - .max = max, - .always_allow_zero = true, - }); - } -}; - -fn handleTransformSyncStreamError(err: anyerror, globalThis: *JSC.JSGlobalObject, err_msg: ?[*:0]const u8, closed: *bool) JSC.JSValue { - switch (err) { - error.ZlibError => { - globalThis.throw("{s}", .{std.mem.sliceTo(err_msg.?, 0)}); - }, - else => { - globalThis.throw("ZlibError: {s}", .{@errorName(err)}); - }, - } - closed.* = true; - return .zero; -} diff --git a/src/bun.js/api/zlib.classes.ts b/src/bun.js/api/zlib.classes.ts index 6b55a9f7d1..a89af81e32 100644 --- a/src/bun.js/api/zlib.classes.ts +++ b/src/bun.js/api/zlib.classes.ts @@ -2,249 +2,48 @@ import { define } from "../../codegen/class-definitions"; export default [ define({ - name: "BrotliEncoder", + name: "NativeZlib", construct: true, - noConstructor: true, + noConstructor: false, + wantsThis: true, finalize: true, configurable: false, - hasPendingActivity: true, - klass: {}, - JSType: "0b11101110", - values: ["callback"], - proto: { - transform: { - fn: "transform", - length: 2, - }, - transformSync: { - fn: "transformSync", - length: 2, - }, - reset: { - fn: "reset", - length: 0, - }, - bytesWritten: { - getter: "getBytesWritten", - }, - bytesRead: { - // deprecated - value: "bytesWritten", - }, - closed: { - getter: "getClosed", - }, - close: { - fn: "close", - length: 0, - }, - chunkSize: { - getter: "getChunkSize", - }, - flush: { - getter: "getFlush", - }, - finishFlush: { - getter: "getFinishFlush", - }, - fullFlush: { - getter: "getFullFlush", - }, - maxOutputLength: { - getter: "getMaxOutputLength", - }, - }, - }), - define({ - name: "BrotliDecoder", - construct: true, - noConstructor: true, - finalize: true, - configurable: false, - hasPendingActivity: true, + // estimatedSize: true, klass: {}, JSType: "0b11101110", values: ["callback"], proto: { - transform: { - fn: "transform", - length: 2, - }, - transformSync: { - fn: "transformSync", - length: 2, - }, - reset: { - fn: "reset", - length: 0, - }, - bytesWritten: { - getter: "getBytesWritten", - }, - bytesRead: { - // deprecated - value: "bytesWritten", - }, - closed: { - getter: "getClosed", - }, - close: { - fn: "close", - length: 0, - }, - chunkSize: { - getter: "getChunkSize", - }, - flush: { - getter: "getFlush", - }, - finishFlush: { - getter: "getFinishFlush", - }, - fullFlush: { - getter: "getFullFlush", - }, - maxOutputLength: { - getter: "getMaxOutputLength", - }, + init: { fn: "init" }, + write: { fn: "write" }, + writeSync: { fn: "writeSync" }, + params: { fn: "params" }, + reset: { fn: "reset" }, + close: { fn: "close" }, + onerror: { setter: "setOnError", getter: "getOnError" }, }, }), + define({ - name: "ZlibEncoder", + name: "NativeBrotli", construct: true, - noConstructor: true, + noConstructor: false, + wantsThis: true, finalize: true, configurable: false, - hasPendingActivity: true, - klass: {}, - JSType: "0b11101110", - values: ["callback"], - proto: { - transform: { - fn: "transform", - length: 2, - }, - transformSync: { - fn: "transformSync", - length: 2, - }, - transformWith: { - fn: "transformWith", - length: 4, - }, - reset: { - fn: "reset", - length: 0, - }, - bytesWritten: { - getter: "getBytesWritten", - }, - bytesRead: { - // deprecated - value: "bytesWritten", - }, - level: { - getter: "getLevel", - }, - strategy: { - getter: "getStrategy", - }, - closed: { - getter: "getClosed", - }, - close: { - fn: "close", - length: 0, - }, - params: { - fn: "params", - length: 3, - }, - chunkSize: { - getter: "getChunkSize", - }, - flush: { - getter: "getFlush", - }, - finishFlush: { - getter: "getFinishFlush", - }, - fullFlush: { - getter: "getFullFlush", - }, - maxOutputLength: { - getter: "getMaxOutputLength", - }, - }, - }), - define({ - name: "ZlibDecoder", - construct: true, - noConstructor: true, - finalize: true, - configurable: false, - hasPendingActivity: true, + estimatedSize: true, klass: {}, JSType: "0b11101110", values: ["callback"], proto: { - transform: { - fn: "transform", - length: 2, - }, - transformSync: { - fn: "transformSync", - length: 2, - }, - transformWith: { - fn: "transformWith", - length: 4, - }, - reset: { - fn: "reset", - length: 0, - }, - bytesWritten: { - getter: "getBytesWritten", - }, - bytesRead: { - // deprecated - value: "bytesWritten", - }, - level: { - getter: "getLevel", - }, - strategy: { - getter: "getStrategy", - }, - closed: { - getter: "getClosed", - }, - close: { - fn: "close", - length: 0, - }, - params: { - fn: "params", - length: 3, - }, - chunkSize: { - getter: "getChunkSize", - }, - flush: { - getter: "getFlush", - }, - finishFlush: { - getter: "getFinishFlush", - }, - fullFlush: { - getter: "getFullFlush", - }, - maxOutputLength: { - getter: "getMaxOutputLength", - }, + init: { fn: "init" }, + write: { fn: "write" }, + writeSync: { fn: "writeSync" }, + params: { fn: "params" }, + reset: { fn: "reset" }, + close: { fn: "close" }, + onerror: { setter: "setOnError", getter: "getOnError" }, }, }), ]; diff --git a/src/bun.js/base.zig b/src/bun.js/base.zig index 94b2e07c14..5eeb82f129 100644 --- a/src/bun.js/base.zig +++ b/src/bun.js/base.zig @@ -553,7 +553,7 @@ pub const ArrayBuffer = extern struct { /// new ArrayBuffer(view.buffer, view.byteOffset, view.byteLength) /// ``` pub inline fn byteSlice(this: *const @This()) []u8 { - return this.ptr[this.offset .. this.offset + this.byte_len]; + return this.ptr[this.offset..][0..this.byte_len]; } /// The equivalent of @@ -564,15 +564,15 @@ pub const ArrayBuffer = extern struct { pub const slice = byteSlice; pub inline fn asU16(this: *const @This()) []u16 { - return std.mem.bytesAsSlice(u16, @as([*]u16, @alignCast(this.ptr))[this.offset..this.byte_len]); + return std.mem.bytesAsSlice(u16, @as([*]u16, @ptrCast(@alignCast(this.ptr)))[this.offset..this.byte_len]); } pub inline fn asU16Unaligned(this: *const @This()) []align(1) u16 { - return std.mem.bytesAsSlice(u16, @as([*]align(1) u16, @alignCast(this.ptr))[this.offset..this.byte_len]); + return std.mem.bytesAsSlice(u16, @as([*]align(1) u16, @ptrCast(@alignCast(this.ptr)))[this.offset..this.byte_len]); } pub inline fn asU32(this: *const @This()) []u32 { - return std.mem.bytesAsSlice(u32, @as([*]u32, @alignCast(this.ptr))[this.offset..this.byte_len]); + return std.mem.bytesAsSlice(u32, @as([*]u32, @ptrCast(@alignCast(this.ptr)))[this.offset..this.byte_len]); } }; diff --git a/src/bun.js/bindings/.clang-format b/src/bun.js/bindings/.clang-format index d8fb9d0b97..79808145f8 100644 --- a/src/bun.js/bindings/.clang-format +++ b/src/bun.js/bindings/.clang-format @@ -11,7 +11,7 @@ AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: All -AllowShortIfStatementsOnASingleLine: false +AllowShortIfStatementsOnASingleLine: true AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None diff --git a/src/bun.js/bindings/ErrorCode.cpp b/src/bun.js/bindings/ErrorCode.cpp index a962c516df..08d0685998 100644 --- a/src/bun.js/bindings/ErrorCode.cpp +++ b/src/bun.js/bindings/ErrorCode.cpp @@ -303,11 +303,181 @@ WTF::String ERR_OUT_OF_RANGE(JSC::ThrowScope& scope, JSC::JSGlobalObject* global return makeString("The value of \""_s, arg_name, "\" is out of range. It must be "_s, range, ". Received: \""_s, input, '"'); } + +} + +namespace ERR { + +JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral val_arg_name, ASCIILiteral val_expected_type, JSC::JSValue val_actual_value, bool instance) +{ + auto arg_name = val_arg_name.span8(); + ASSERT(WTF::charactersAreAllASCII(arg_name)); + + auto expected_type = val_expected_type.span8(); + ASSERT(WTF::charactersAreAllASCII(expected_type)); + + auto actual_value = JSValueToStringSafe(globalObject, val_actual_value); + RETURN_IF_EXCEPTION(throwScope, {}); + + if (instance) { + auto message = makeString("The \""_s, arg_name, "\" argument must be an instance of "_s, expected_type, ". Received "_s, actual_value); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INVALID_ARG_TYPE, message)); + return {}; + } + + auto message = makeString("The \""_s, arg_name, "\" argument must be of type "_s, expected_type, ". Received "_s, actual_value); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INVALID_ARG_TYPE, message)); + return {}; +} +JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue val_arg_name, ASCIILiteral val_expected_type, JSC::JSValue val_actual_value, bool instance) +{ + auto arg_name = val_arg_name.toWTFString(globalObject); + RETURN_IF_EXCEPTION(throwScope, {}); + + auto expected_type = val_expected_type.span8(); + ASSERT(WTF::charactersAreAllASCII(expected_type)); + + auto actual_value = JSValueToStringSafe(globalObject, val_actual_value); + RETURN_IF_EXCEPTION(throwScope, {}); + + if (instance) { + auto message = makeString("The \""_s, arg_name, "\" argument must be an instance of "_s, expected_type, ". Received "_s, actual_value); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INVALID_ARG_TYPE, message)); + return {}; + } + + auto message = makeString("The \""_s, arg_name, "\" argument must be of type "_s, expected_type, ". Received "_s, actual_value); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INVALID_ARG_TYPE, message)); + return {}; +} + +JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral arg_name, size_t lower, size_t upper, JSC::JSValue actual) +{ + auto lowerStr = jsNumber(lower).toWTFString(globalObject); + auto upperStr = jsNumber(upper).toWTFString(globalObject); + auto actual_value = JSValueToStringSafe(globalObject, actual); + RETURN_IF_EXCEPTION(throwScope, {}); + + auto message = makeString("The value of \""_s, arg_name, "\" is out of range. It must be >= "_s, lowerStr, " and <= "_s, upperStr, ". Received "_s, actual_value); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_OUT_OF_RANGE, message)); + return {}; +} +JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name_val, size_t lower, size_t upper, JSC::JSValue actual) +{ + auto arg_name = arg_name_val.toWTFString(globalObject); + RETURN_IF_EXCEPTION(throwScope, {}); + auto lowerStr = jsNumber(lower).toWTFString(globalObject); + auto upperStr = jsNumber(upper).toWTFString(globalObject); + auto actual_value = JSValueToStringSafe(globalObject, actual); + RETURN_IF_EXCEPTION(throwScope, {}); + + auto message = makeString("The value of \""_s, arg_name, "\" is out of range. It must be >= "_s, lowerStr, " and <= "_s, upperStr, ". Received "_s, actual_value); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_OUT_OF_RANGE, message)); + return {}; +} +JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name_val, size_t bound_num, Bound bound, JSC::JSValue actual) +{ + auto arg_name = arg_name_val.toWTFString(globalObject); + RETURN_IF_EXCEPTION(throwScope, {}); + auto actual_value = JSValueToStringSafe(globalObject, actual); + RETURN_IF_EXCEPTION(throwScope, {}); + + switch (bound) { + case LOWER: { + auto message = makeString("The value of \""_s, arg_name, "\" is out of range. It must be >= "_s, bound_num, ". Received "_s, actual_value); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_OUT_OF_RANGE, message)); + return {}; + } + case UPPER: { + auto message = makeString("The value of \""_s, arg_name, "\" is out of range. It must be <= "_s, bound_num, ". Received "_s, actual_value); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_OUT_OF_RANGE, message)); + return {}; + } + } +} +JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name_val, ASCIILiteral msg, JSC::JSValue actual) +{ + auto arg_name = arg_name_val.toWTFString(globalObject); + RETURN_IF_EXCEPTION(throwScope, {}); + auto actual_value = JSValueToStringSafe(globalObject, actual); + RETURN_IF_EXCEPTION(throwScope, {}); + + auto message = makeString("The value of \""_s, arg_name, "\" is out of range. It must be "_s, msg, ". Received "_s, actual_value); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_OUT_OF_RANGE, message)); + return {}; +} +JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral arg_name, ASCIILiteral msg, JSC::JSValue actual) +{ + auto actual_value = JSValueToStringSafe(globalObject, actual); + RETURN_IF_EXCEPTION(throwScope, {}); + + auto message = makeString("The value of \""_s, arg_name, "\" is out of range. It must be "_s, msg, ". Received "_s, actual_value); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_OUT_OF_RANGE, message)); + return {}; +} + +JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral name, JSC::JSValue value, ASCIILiteral reason) +{ + ASCIILiteral type; + { + auto sp = name.span8(); + auto str = std::string_view((const char*)(sp.data()), sp.size()); + auto has = str.find('.') == std::string::npos; + type = has ? "property"_s : "argument"_s; + } + + auto value_string = JSValueToStringSafe(globalObject, value); + RETURN_IF_EXCEPTION(throwScope, {}); + + auto message = makeString("The "_s, type, " '"_s, name, "' "_s, reason, ". Received "_s, value_string); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INVALID_ARG_VALUE, message)); + return {}; +} + +JSC::EncodedJSValue UNKNOWN_ENCODING(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue encoding) +{ + auto encoding_string = JSValueToStringSafe(globalObject, encoding); + RETURN_IF_EXCEPTION(throwScope, {}); + + auto message = makeString("Unknown encoding: "_s, encoding_string); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_UNKNOWN_ENCODING, message)); + return {}; +} + +JSC::EncodedJSValue INVALID_STATE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral statemsg) +{ + auto message = makeString("Invalid state: "_s, statemsg); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INVALID_STATE, message)); + return {}; +} + +JSC::EncodedJSValue STRING_TOO_LONG(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject) +{ + auto message = makeString("Cannot create a string longer than "_s, WTF::String::MaxLength, " characters"_s); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_STRING_TOO_LONG, message)); + return {}; +} + +JSC::EncodedJSValue BUFFER_OUT_OF_BOUNDS(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject) +{ + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_BUFFER_OUT_OF_BOUNDS, "Attempt to access memory outside buffer bounds"_s)); + return {}; +} + +JSC::EncodedJSValue UNKNOWN_SIGNAL(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue signal) +{ + auto signal_string = JSValueToStringSafe(globalObject, signal); + RETURN_IF_EXCEPTION(throwScope, {}); + + auto message = makeString("Unknown signal: "_s, signal_string); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_UNKNOWN_SIGNAL, message)); + return {}; +} + } static JSC::JSValue ERR_INVALID_ARG_TYPE(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue arg0, JSValue arg1, JSValue arg2) { - if (auto* array = jsDynamicCast(arg1)) { const WTF::String argName = arg0.toWTFString(globalObject); RETURN_IF_EXCEPTION(scope, {}); @@ -487,6 +657,39 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_INVALID_ARG_TYPE, (JSC::JSGlobalObject * return JSValue::encode(ERR_INVALID_ARG_TYPE(scope, globalObject, arg_name, expected_type, actual_value)); } +JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_BROTLI_INVALID_PARAM, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + EXPECT_ARG_COUNT(1); + + auto param = callFrame->argument(0).toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + + auto message = makeString(param, " is not a valid Brotli parameter"_s); + return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_BROTLI_INVALID_PARAM, message)); +} + +JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_BUFFER_TOO_LARGE, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + EXPECT_ARG_COUNT(1); + + auto param = callFrame->argument(0).toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + + auto message = makeString("Cannot create a Buffer larger than "_s, param, " bytes"_s); + return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_BUFFER_TOO_LARGE, message)); +} + +JSC_DEFINE_HOST_FUNCTION(jsFunction_ERR_ZLIB_INITIALIZATION_FAILED, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_ZLIB_INITIALIZATION_FAILED, "Initialization failed"_s)); +} + } // namespace Bun JSC::JSValue WebCore::toJS(JSC::JSGlobalObject* globalObject, CommonAbortReason abortReason) diff --git a/src/bun.js/bindings/ErrorCode.h b/src/bun.js/bindings/ErrorCode.h index 4e2d1d6010..609050b36c 100644 --- a/src/bun.js/bindings/ErrorCode.h +++ b/src/bun.js/bindings/ErrorCode.h @@ -63,5 +63,31 @@ JSC_DECLARE_HOST_FUNCTION(jsFunction_ERR_IPC_CHANNEL_CLOSED); JSC_DECLARE_HOST_FUNCTION(jsFunction_ERR_SOCKET_BAD_TYPE); JSC_DECLARE_HOST_FUNCTION(jsFunction_ERR_INVALID_PROTOCOL); JSC_DECLARE_HOST_FUNCTION(jsFunctionMakeErrorWithCode); +JSC_DECLARE_HOST_FUNCTION(jsFunction_ERR_BROTLI_INVALID_PARAM); +JSC_DECLARE_HOST_FUNCTION(jsFunction_ERR_BUFFER_TOO_LARGE); +JSC_DECLARE_HOST_FUNCTION(jsFunction_ERR_ZLIB_INITIALIZATION_FAILED); + +enum Bound { + LOWER, + UPPER, +}; + +namespace ERR { + +JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral val_arg_name, ASCIILiteral val_expected_type, JSC::JSValue val_actual_value, bool instance = false); +JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue val_arg_name, ASCIILiteral val_expected_type, JSC::JSValue val_actual_value, bool instance = false); +JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral arg_name, size_t lower, size_t upper, JSC::JSValue actual); +JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name, size_t lower, size_t upper, JSC::JSValue actual); +JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name_val, size_t bound_num, Bound bound, JSC::JSValue actual); +JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name_val, ASCIILiteral msg, JSC::JSValue actual); +JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral arg_name_val, ASCIILiteral msg, JSC::JSValue actual); +JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral name, JSC::JSValue value, ASCIILiteral reason = "is invalid"_s); +JSC::EncodedJSValue UNKNOWN_ENCODING(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue encoding); +JSC::EncodedJSValue INVALID_STATE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral statemsg); +JSC::EncodedJSValue STRING_TOO_LONG(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject); +JSC::EncodedJSValue BUFFER_OUT_OF_BOUNDS(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject); +JSC::EncodedJSValue UNKNOWN_SIGNAL(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue signal); + +} } diff --git a/src/bun.js/bindings/ErrorCode.ts b/src/bun.js/bindings/ErrorCode.ts index c8519af883..549225769e 100644 --- a/src/bun.js/bindings/ErrorCode.ts +++ b/src/bun.js/bindings/ErrorCode.ts @@ -42,6 +42,11 @@ export default [ ["ERR_ILLEGAL_CONSTRUCTOR", TypeError, "TypeError"], ["ERR_INVALID_URL", TypeError, "TypeError"], ["ERR_BUFFER_TOO_LARGE", RangeError, "RangeError"], + ["ERR_BROTLI_INVALID_PARAM", RangeError, "RangeError"], + ["ERR_UNKNOWN_ENCODING", TypeError, "TypeError"], + ["ERR_INVALID_STATE", Error, "Error"], + ["ERR_BUFFER_OUT_OF_BOUNDS", RangeError, "RangeError"], + ["ERR_UNKNOWN_SIGNAL", TypeError, "TypeError"], // Bun-specific ["ERR_FORMDATA_PARSE_ERROR", TypeError, "TypeError"], diff --git a/src/bun.js/bindings/NodeValidator.cpp b/src/bun.js/bindings/NodeValidator.cpp new file mode 100644 index 0000000000..a3e4af5f05 --- /dev/null +++ b/src/bun.js/bindings/NodeValidator.cpp @@ -0,0 +1,166 @@ +#include "root.h" + +#include "ZigGlobalObject.h" +#include "JavaScriptCore/JSGlobalObject.h" +#include "JavaScriptCore/JSCJSValue.h" +#include "JavaScriptCore/ExceptionScope.h" +#include "JavaScriptCore/CallData.h" +#include "JavaScriptCore/JSObjectInlines.h" +#include +#include + +#include "ErrorCode.h" +#include "NodeValidator.h" + +namespace Bun { + +using namespace JSC; + +JSC_DEFINE_HOST_FUNCTION(jsFunction_validateInteger, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto value = callFrame->argument(0); + auto name = callFrame->argument(1); + auto min = callFrame->argument(2); + auto max = callFrame->argument(3); + + if (!value.isNumber()) return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, name, "number"_s, value); + if (min.isUndefined()) min = jsNumber(-9007199254740991); // Number.MIN_SAFE_INTEGER + if (max.isUndefined()) max = jsNumber(9007199254740991); // Number.MAX_SAFE_INTEGER + + auto value_num = value.asNumber(); + auto min_num = min.toNumber(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + auto max_num = max.toNumber(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + max_num = std::max(min_num, max_num); + + if (std::fmod(value_num, 1.0) != 0) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, "an integer"_s, value); + if (value_num < min_num || value_num > max_num) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, min_num, max_num, value); + + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsFunction_validateNumber, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto value = callFrame->argument(0); + auto name = callFrame->argument(1); + auto min = callFrame->argument(2); + auto max = callFrame->argument(3); + return Bun::validateNumber(scope, globalObject, value, name, min, max); +} +JSC::EncodedJSValue validateNumber(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, JSValue name, JSValue min, JSValue max) +{ + if (!value.isNumber()) return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, name, "number"_s, value); + + auto value_num = value.asNumber(); + auto min_num = min.toNumber(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + auto max_num = max.toNumber(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + + auto min_isnonnull = !min.isUndefinedOrNull(); + auto max_isnonnull = !max.isUndefinedOrNull(); + + if ((min_isnonnull && value_num < min_num) || (max_isnonnull && value_num > max_num) || ((min_isnonnull || max_isnonnull) && std::isnan(value_num))) { + if (min_isnonnull && max_isnonnull) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, min_num, max_num, value); + if (min_isnonnull) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, min_num, Bun::LOWER, value); + if (max_isnonnull) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, max_num, Bun::UPPER, value); + return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, ""_s, value); + } + + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsFunction_validateString, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto value = callFrame->argument(0); + auto name = callFrame->argument(1); + + if (!value.isString()) { + return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, name, "string"_s, value); + } + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsFunction_validateFiniteNumber, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto number = callFrame->argument(0); + auto name = callFrame->argument(1); + return Bun::validateFiniteNumber(scope, globalObject, number, name); +} +JSC::EncodedJSValue validateFiniteNumber(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue number, JSValue name) +{ + if (number.isUndefined()) { + return JSValue::encode(jsBoolean(false)); + } + if (number.isNumber() && (!std::isnan(number.asNumber())) && (!std::isinf(number.asNumber()))) { + return JSValue::encode(jsBoolean(true)); + } + if (number.isNumber() && std::isnan(number.asNumber())) { + return JSValue::encode(jsBoolean(false)); + } + + Bun::validateNumber(scope, globalObject, number, name, jsUndefined(), jsUndefined()); + RETURN_IF_EXCEPTION(scope, {}); + + return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, "a finite number"_s, number); +} + +JSC_DEFINE_HOST_FUNCTION(jsFunction_checkRangesOrGetDefault, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto number = callFrame->argument(0); + auto name = callFrame->argument(1); + auto lower = callFrame->argument(2); + auto upper = callFrame->argument(3); + auto def = callFrame->argument(4); + + auto finite = Bun::validateFiniteNumber(scope, globalObject, number, name); + RETURN_IF_EXCEPTION(scope, {}); + auto finite_real = JSValue::decode(finite).asBoolean(); + if (!finite_real) { + return JSValue::encode(def); + } + + auto number_num = number.toNumber(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + auto lower_num = lower.toNumber(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + auto upper_num = upper.toNumber(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + + if (number_num < lower_num || number_num > upper_num) { + return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, lower_num, upper_num, number); + } + return JSValue::encode(number); +} + +JSC_DEFINE_HOST_FUNCTION(jsFunction_validateFunction, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto value = callFrame->argument(0); + auto name = callFrame->argument(1); + + if (JSC::getCallData(value).type == JSC::CallData::Type::None) { + return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, name, "function"_s, value); + } + return JSValue::encode(jsUndefined()); +} + +} diff --git a/src/bun.js/bindings/NodeValidator.h b/src/bun.js/bindings/NodeValidator.h new file mode 100644 index 0000000000..2c8578da1b --- /dev/null +++ b/src/bun.js/bindings/NodeValidator.h @@ -0,0 +1,21 @@ +#include "root.h" + +#include "ZigGlobalObject.h" +#include "ErrorCode.h" +#include "JavaScriptCore/JSCJSValue.h" + +namespace Bun { + +JSC_DEFINE_HOST_FUNCTION(jsFunction_validateInteger, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)); +JSC_DEFINE_HOST_FUNCTION(jsFunction_validateNumber, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)); +JSC_DEFINE_HOST_FUNCTION(jsFunction_validateString, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)); +JSC_DEFINE_HOST_FUNCTION(jsFunction_validateFiniteNumber, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)); +JSC_DEFINE_HOST_FUNCTION(jsFunction_checkRangesOrGetDefault, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)); +JSC_DEFINE_HOST_FUNCTION(jsFunction_validateFunction, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)); + +JSC::EncodedJSValue validateNumber(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSC::JSValue value, JSC::JSValue name, JSC::JSValue min, JSC::JSValue max); +JSC::EncodedJSValue validateFiniteNumber(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSC::JSValue number, JSC::JSValue name); + +} + +// validateFunction diff --git a/src/bun.js/bindings/ProcessBindingConstants.cpp b/src/bun.js/bindings/ProcessBindingConstants.cpp index 8db61a6e72..6d2fb1a2f1 100644 --- a/src/bun.js/bindings/ProcessBindingConstants.cpp +++ b/src/bun.js/bindings/ProcessBindingConstants.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include #include #include @@ -1020,76 +1022,76 @@ static JSValue processBindingConstantsGetZlib(VM& vm, JSObject* bindingObject) object->putDirect(vm, PropertyName(Identifier::fromString(vm, "Z_MAX_WINDOWBITS"_s)), jsNumber(15)); object->putDirect(vm, PropertyName(Identifier::fromString(vm, "Z_DEFAULT_WINDOWBITS"_s)), jsNumber(15)); object->putDirect(vm, PropertyName(Identifier::fromString(vm, "Z_MIN_CHUNK"_s)), jsNumber(64)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "Z_MAX_CHUNK"_s)), jsNumber(INFINITY)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "Z_DEFAULT_CHUNK"_s)), jsNumber(16384)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "Z_MAX_CHUNK"_s)), jsNumber(std::numeric_limits::infinity())); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "Z_DEFAULT_CHUNK"_s)), jsNumber(16 * 1024)); object->putDirect(vm, PropertyName(Identifier::fromString(vm, "Z_MIN_MEMLEVEL"_s)), jsNumber(1)); object->putDirect(vm, PropertyName(Identifier::fromString(vm, "Z_MAX_MEMLEVEL"_s)), jsNumber(9)); object->putDirect(vm, PropertyName(Identifier::fromString(vm, "Z_DEFAULT_MEMLEVEL"_s)), jsNumber(8)); object->putDirect(vm, PropertyName(Identifier::fromString(vm, "Z_MIN_LEVEL"_s)), jsNumber(-1)); object->putDirect(vm, PropertyName(Identifier::fromString(vm, "Z_MAX_LEVEL"_s)), jsNumber(9)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "Z_DEFAULT_LEVEL"_s)), jsNumber(-1)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "Z_DEFAULT_LEVEL"_s)), jsNumber(Z_DEFAULT_COMPRESSION)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_OPERATION_PROCESS"_s)), jsNumber(0)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_OPERATION_FLUSH"_s)), jsNumber(1)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_OPERATION_FINISH"_s)), jsNumber(2)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_OPERATION_EMIT_METADATA"_s)), jsNumber(3)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_PARAM_MODE"_s)), jsNumber(0)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_MODE_GENERIC"_s)), jsNumber(0)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_MODE_TEXT"_s)), jsNumber(1)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_MODE_FONT"_s)), jsNumber(2)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DEFAULT_MODE"_s)), jsNumber(0)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_PARAM_QUALITY"_s)), jsNumber(1)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_MIN_QUALITY"_s)), jsNumber(0)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_MAX_QUALITY"_s)), jsNumber(11)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DEFAULT_QUALITY"_s)), jsNumber(11)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_PARAM_LGWIN"_s)), jsNumber(2)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_MIN_WINDOW_BITS"_s)), jsNumber(10)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_MAX_WINDOW_BITS"_s)), jsNumber(24)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_LARGE_MAX_WINDOW_BITS"_s)), jsNumber(30)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DEFAULT_WINDOW"_s)), jsNumber(22)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_PARAM_LGBLOCK"_s)), jsNumber(3)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_MIN_INPUT_BLOCK_BITS"_s)), jsNumber(16)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_MAX_INPUT_BLOCK_BITS"_s)), jsNumber(24)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING"_s)), jsNumber(4)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_PARAM_SIZE_HINT"_s)), jsNumber(5)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_PARAM_LARGE_WINDOW"_s)), jsNumber(6)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_PARAM_NPOSTFIX"_s)), jsNumber(7)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_PARAM_NDIRECT"_s)), jsNumber(8)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_RESULT_ERROR"_s)), jsNumber(0)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_RESULT_SUCCESS"_s)), jsNumber(1)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT"_s)), jsNumber(2)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT"_s)), jsNumber(3)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION"_s)), jsNumber(0)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_PARAM_LARGE_WINDOW"_s)), jsNumber(1)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_NO_ERROR"_s)), jsNumber(0)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_SUCCESS"_s)), jsNumber(1)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_NEEDS_MORE_INPUT"_s)), jsNumber(2)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_NEEDS_MORE_OUTPUT"_s)), jsNumber(3)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_NIBBLE"_s)), jsNumber(-1)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_RESERVED"_s)), jsNumber(-2)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_META_NIBBLE"_s)), jsNumber(-3)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_ALPHABET"_s)), jsNumber(-4)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_SAME"_s)), jsNumber(-5)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_CL_SPACE"_s)), jsNumber(-6)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_HUFFMAN_SPACE"_s)), jsNumber(-7)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_CONTEXT_MAP_REPEAT"_s)), jsNumber(-8)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_1"_s)), jsNumber(-9)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_2"_s)), jsNumber(-10)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_TRANSFORM"_s)), jsNumber(-11)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_DICTIONARY"_s)), jsNumber(-12)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_WINDOW_BITS"_s)), jsNumber(-13)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_PADDING_1"_s)), jsNumber(-14)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_PADDING_2"_s)), jsNumber(-15)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_DISTANCE"_s)), jsNumber(-16)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_DICTIONARY_NOT_SET"_s)), jsNumber(-19)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_INVALID_ARGUMENTS"_s)), jsNumber(-20)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MODES"_s)), jsNumber(-21)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_ALLOC_TREE_GROUPS"_s)), jsNumber(-22)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MAP"_s)), jsNumber(-25)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_1"_s)), jsNumber(-26)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_2"_s)), jsNumber(-27)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES"_s)), jsNumber(-30)); - object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_UNREACHABLE"_s)), jsNumber(-31)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_OPERATION_PROCESS"_s)), jsNumber(BROTLI_OPERATION_PROCESS)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_OPERATION_FLUSH"_s)), jsNumber(BROTLI_OPERATION_FLUSH)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_OPERATION_FINISH"_s)), jsNumber(BROTLI_OPERATION_FINISH)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_OPERATION_EMIT_METADATA"_s)), jsNumber(BROTLI_OPERATION_EMIT_METADATA)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_PARAM_MODE"_s)), jsNumber(BROTLI_PARAM_MODE)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_MODE_GENERIC"_s)), jsNumber(BROTLI_MODE_GENERIC)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_MODE_TEXT"_s)), jsNumber(BROTLI_MODE_TEXT)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_MODE_FONT"_s)), jsNumber(BROTLI_MODE_FONT)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DEFAULT_MODE"_s)), jsNumber(BROTLI_DEFAULT_MODE)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_PARAM_QUALITY"_s)), jsNumber(BROTLI_PARAM_QUALITY)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_MIN_QUALITY"_s)), jsNumber(BROTLI_MIN_QUALITY)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_MAX_QUALITY"_s)), jsNumber(BROTLI_MAX_QUALITY)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DEFAULT_QUALITY"_s)), jsNumber(BROTLI_DEFAULT_QUALITY)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_PARAM_LGWIN"_s)), jsNumber(BROTLI_PARAM_LGWIN)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_MIN_WINDOW_BITS"_s)), jsNumber(BROTLI_MIN_WINDOW_BITS)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_MAX_WINDOW_BITS"_s)), jsNumber(BROTLI_MAX_WINDOW_BITS)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_LARGE_MAX_WINDOW_BITS"_s)), jsNumber(BROTLI_LARGE_MAX_WINDOW_BITS)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DEFAULT_WINDOW"_s)), jsNumber(BROTLI_DEFAULT_WINDOW)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_PARAM_LGBLOCK"_s)), jsNumber(BROTLI_PARAM_LGBLOCK)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_MIN_INPUT_BLOCK_BITS"_s)), jsNumber(BROTLI_MIN_INPUT_BLOCK_BITS)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_MAX_INPUT_BLOCK_BITS"_s)), jsNumber(BROTLI_MAX_INPUT_BLOCK_BITS)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING"_s)), jsNumber(BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_PARAM_SIZE_HINT"_s)), jsNumber(BROTLI_PARAM_SIZE_HINT)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_PARAM_LARGE_WINDOW"_s)), jsNumber(BROTLI_PARAM_LARGE_WINDOW)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_PARAM_NPOSTFIX"_s)), jsNumber(BROTLI_PARAM_NPOSTFIX)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_PARAM_NDIRECT"_s)), jsNumber(BROTLI_PARAM_NDIRECT)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_RESULT_ERROR"_s)), jsNumber(BROTLI_DECODER_RESULT_ERROR)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_RESULT_SUCCESS"_s)), jsNumber(BROTLI_DECODER_RESULT_SUCCESS)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT"_s)), jsNumber(BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT"_s)), jsNumber(BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION"_s)), jsNumber(BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_PARAM_LARGE_WINDOW"_s)), jsNumber(BROTLI_DECODER_PARAM_LARGE_WINDOW)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_NO_ERROR"_s)), jsNumber(BROTLI_DECODER_NO_ERROR)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_SUCCESS"_s)), jsNumber(BROTLI_DECODER_SUCCESS)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_NEEDS_MORE_INPUT"_s)), jsNumber(BROTLI_DECODER_NEEDS_MORE_INPUT)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_NEEDS_MORE_OUTPUT"_s)), jsNumber(BROTLI_DECODER_NEEDS_MORE_OUTPUT)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_NIBBLE"_s)), jsNumber(BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_NIBBLE)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_RESERVED"_s)), jsNumber(BROTLI_DECODER_ERROR_FORMAT_RESERVED)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_META_NIBBLE"_s)), jsNumber(BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_META_NIBBLE)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_ALPHABET"_s)), jsNumber(BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_ALPHABET)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_SAME"_s)), jsNumber(BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_SAME)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_CL_SPACE"_s)), jsNumber(BROTLI_DECODER_ERROR_FORMAT_CL_SPACE)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_HUFFMAN_SPACE"_s)), jsNumber(BROTLI_DECODER_ERROR_FORMAT_HUFFMAN_SPACE)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_CONTEXT_MAP_REPEAT"_s)), jsNumber(BROTLI_DECODER_ERROR_FORMAT_CONTEXT_MAP_REPEAT)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_1"_s)), jsNumber(BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_1)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_2"_s)), jsNumber(BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_2)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_TRANSFORM"_s)), jsNumber(BROTLI_DECODER_ERROR_FORMAT_TRANSFORM)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_DICTIONARY"_s)), jsNumber(BROTLI_DECODER_ERROR_FORMAT_DICTIONARY)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_WINDOW_BITS"_s)), jsNumber(BROTLI_DECODER_ERROR_FORMAT_WINDOW_BITS)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_PADDING_1"_s)), jsNumber(BROTLI_DECODER_ERROR_FORMAT_PADDING_1)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_PADDING_2"_s)), jsNumber(BROTLI_DECODER_ERROR_FORMAT_PADDING_2)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_FORMAT_DISTANCE"_s)), jsNumber(BROTLI_DECODER_ERROR_FORMAT_DISTANCE)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_DICTIONARY_NOT_SET"_s)), jsNumber(BROTLI_DECODER_ERROR_DICTIONARY_NOT_SET)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_INVALID_ARGUMENTS"_s)), jsNumber(BROTLI_DECODER_ERROR_INVALID_ARGUMENTS)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MODES"_s)), jsNumber(BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MODES)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_ALLOC_TREE_GROUPS"_s)), jsNumber(BROTLI_DECODER_ERROR_ALLOC_TREE_GROUPS)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MAP"_s)), jsNumber(BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MAP)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_1"_s)), jsNumber(BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_1)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_2"_s)), jsNumber(BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_2)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES"_s)), jsNumber(BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES)); + object->putDirect(vm, PropertyName(Identifier::fromString(vm, "BROTLI_DECODER_ERROR_UNREACHABLE"_s)), jsNumber(BROTLI_DECODER_ERROR_UNREACHABLE)); return object; } diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index 2e9d427843..c7413c2651 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -6363,6 +6363,10 @@ pub const CallFrame = opaque { pub const name = "JSC::CallFrame"; + inline fn asUnsafeJSValueArray(self: *const CallFrame) [*]const JSC.JSValue { + return @as([*]align(alignment) const JSC.JSValue, @ptrCast(@alignCast(self))); + } + pub fn format(frame: *CallFrame, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { const args = frame.argumentsPtr()[0..frame.argumentsCount()]; @@ -6392,17 +6396,18 @@ pub const CallFrame = opaque { } pub fn argumentsPtr(self: *const CallFrame) [*]const JSC.JSValue { - return @as([*]align(alignment) const JSC.JSValue, @ptrCast(@alignCast(self))) + Sizes.Bun_CallFrame__firstArgument; + return self.asUnsafeJSValueArray()[Sizes.Bun_CallFrame__firstArgument..]; } pub fn callee(self: *const CallFrame) JSC.JSValue { - return (@as([*]align(alignment) const JSC.JSValue, @ptrCast(@alignCast(self))) + Sizes.Bun_CallFrame__callee)[0]; + return self.asUnsafeJSValueArray()[Sizes.Bun_CallFrame__callee]; } fn Arguments(comptime max: usize) type { return struct { ptr: [max]JSC.JSValue, len: usize, + pub inline fn init(comptime i: usize, ptr: [*]const JSC.JSValue) @This() { var args: [max]JSC.JSValue = std.mem.zeroes([max]JSC.JSValue); args[0..i].* = ptr[0..i].*; @@ -6413,6 +6418,12 @@ pub const CallFrame = opaque { }; } + pub inline fn initUndef(comptime i: usize, ptr: [*]const JSC.JSValue) @This() { + var args = [1]JSC.JSValue{.undefined} ** max; + args[0..i].* = ptr[0..i].*; + return @This(){ .ptr = args, .len = i }; + } + pub inline fn slice(self: *const @This()) []const JSValue { return self.ptr[0..self.len]; } @@ -6429,16 +6440,26 @@ pub const CallFrame = opaque { }; } + pub fn argumentsUndef(self: *const CallFrame, comptime max: usize) Arguments(max) { + const len = self.argumentsCount(); + const ptr = self.argumentsPtr(); + return switch (@as(u4, @min(len, max))) { + 0 => .{ .ptr = .{.undefined} ** max, .len = 0 }, + inline 1...9 => |count| Arguments(max).initUndef(@min(count, max), ptr), + else => unreachable, + }; + } + pub fn argument(self: *const CallFrame, comptime i: comptime_int) JSC.JSValue { return self.argumentsPtr()[i]; } pub fn this(self: *const CallFrame) JSC.JSValue { - return (@as([*]align(alignment) const JSC.JSValue, @ptrCast(@alignCast(self))) + Sizes.Bun_CallFrame__thisArgument)[0]; + return self.asUnsafeJSValueArray()[Sizes.Bun_CallFrame__thisArgument]; } pub fn argumentsCount(self: *const CallFrame) usize { - return @as(usize, @intCast((@as([*]align(alignment) const JSC.JSValue, @ptrCast(@alignCast(self))) + Sizes.Bun_CallFrame__argumentCountIncludingThis)[0].asInt32() - 1)); + return @as(usize, @intCast(self.asUnsafeJSValueArray()[Sizes.Bun_CallFrame__argumentCountIncludingThis].asInt32() - 1)); } }; diff --git a/src/bun.js/bindings/generated_classes_list.zig b/src/bun.js/bindings/generated_classes_list.zig index 7550e84910..f32a6a9b19 100644 --- a/src/bun.js/bindings/generated_classes_list.zig +++ b/src/bun.js/bindings/generated_classes_list.zig @@ -73,9 +73,7 @@ pub const Classes = struct { pub const BytesInternalReadableStreamSource = JSC.WebCore.ByteStream.Source; pub const PostgresSQLConnection = JSC.Postgres.PostgresSQLConnection; pub const PostgresSQLQuery = JSC.Postgres.PostgresSQLQuery; - pub const BrotliEncoder = JSC.API.BrotliEncoder; - pub const BrotliDecoder = JSC.API.BrotliDecoder; pub const TextEncoderStreamEncoder = JSC.WebCore.TextEncoderStreamEncoder; - pub const ZlibEncoder = JSC.API.ZlibEncoder; - pub const ZlibDecoder = JSC.API.ZlibDecoder; + pub const NativeZlib = JSC.API.NativeZlib; + pub const NativeBrotli = JSC.API.NativeBrotli; }; diff --git a/src/bun.js/event_loop.zig b/src/bun.js/event_loop.zig index 7a0f7a793e..f8ca831d13 100644 --- a/src/bun.js/event_loop.zig +++ b/src/bun.js/event_loop.zig @@ -380,10 +380,8 @@ const Futimes = JSC.Node.Async.futimes; const Lchmod = JSC.Node.Async.lchmod; const Lchown = JSC.Node.Async.lchown; const Unlink = JSC.Node.Async.unlink; -const BrotliDecoder = JSC.API.BrotliDecoder; -const BrotliEncoder = JSC.API.BrotliEncoder; -const ZlibDecoder = JSC.API.ZlibDecoder; -const ZlibEncoder = JSC.API.ZlibEncoder; +const NativeZlib = JSC.API.NativeZlib; +const NativeBrotli = JSC.API.NativeBrotli; const ShellGlobTask = bun.shell.interpret.Interpreter.Expansion.ShellGlobTask; const ShellRmTask = bun.shell.Interpreter.Builtin.Rm.ShellRmTask; @@ -466,10 +464,8 @@ pub const Task = TaggedPointerUnion(.{ Lchmod, Lchown, Unlink, - BrotliEncoder, - BrotliDecoder, - ZlibEncoder, - ZlibDecoder, + NativeZlib, + NativeBrotli, ShellGlobTask, ShellRmTask, ShellRmDirTask, @@ -1224,20 +1220,12 @@ pub const EventLoop = struct { var any: *Unlink = task.get(Unlink).?; any.runFromJSThread(); }, - @field(Task.Tag, typeBaseName(@typeName(BrotliEncoder))) => { - var any: *BrotliEncoder = task.get(BrotliEncoder).?; + @field(Task.Tag, typeBaseName(@typeName(NativeZlib))) => { + var any: *NativeZlib = task.get(NativeZlib).?; any.runFromJSThread(); }, - @field(Task.Tag, typeBaseName(@typeName(BrotliDecoder))) => { - var any: *BrotliDecoder = task.get(BrotliDecoder).?; - any.runFromJSThread(); - }, - @field(Task.Tag, typeBaseName(@typeName(ZlibEncoder))) => { - var any: *ZlibEncoder = task.get(ZlibEncoder).?; - any.runFromJSThread(); - }, - @field(Task.Tag, typeBaseName(@typeName(ZlibDecoder))) => { - var any: *ZlibDecoder = task.get(ZlibDecoder).?; + @field(Task.Tag, typeBaseName(@typeName(NativeBrotli))) => { + var any: *NativeBrotli = task.get(NativeBrotli).?; any.runFromJSThread(); }, @field(Task.Tag, typeBaseName(@typeName(ProcessWaiterThreadTask))) => { diff --git a/src/bun.js/node/node_crypto_binding.zig b/src/bun.js/node/node_crypto_binding.zig index 026450dc45..430f87f951 100644 --- a/src/bun.js/node/node_crypto_binding.zig +++ b/src/bun.js/node/node_crypto_binding.zig @@ -21,10 +21,10 @@ fn randomInt(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSC.JSV const min = arguments[0].to(i64); const max = arguments[1].to(i64); - if (min > validators.NUMBER__MAX_SAFE_INTEGER or min < validators.NUMBER__MIN_SAFE_INTEGER) { + if (min > JSC.MAX_SAFE_INTEGER or min < JSC.MIN_SAFE_INTEGER) { return globalThis.throwInvalidArgumentRangeValue("min", "It must be a safe integer type number", min); } - if (max > validators.NUMBER__MAX_SAFE_INTEGER) { + if (max > JSC.MAX_SAFE_INTEGER) { return globalThis.throwInvalidArgumentRangeValue("max", "It must be a safe integer type number", max); } if (min >= max) { diff --git a/src/bun.js/node/node_zlib_binding.zig b/src/bun.js/node/node_zlib_binding.zig index 066174d695..5337a64271 100644 --- a/src/bun.js/node/node_zlib_binding.zig +++ b/src/bun.js/node/node_zlib_binding.zig @@ -5,14 +5,7 @@ const JSC = bun.JSC; const string = bun.string; const Output = bun.Output; const ZigString = JSC.ZigString; - -pub const createBrotliEncoder = bun.JSC.API.BrotliEncoder.create; - -pub const createBrotliDecoder = bun.JSC.API.BrotliDecoder.create; - -pub const createZlibEncoder = bun.JSC.API.ZlibEncoder.create; - -pub const createZlibDecoder = bun.JSC.API.ZlibDecoder.create; +const validators = @import("./util/validators.zig"); pub fn crc32(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue { const arguments = callframe.arguments(2).ptr; @@ -68,3 +61,840 @@ pub fn crc32(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callcon const slice_u8 = data.slice(); return JSC.JSValue.jsNumber(@as(u32, @intCast(bun.zlib.crc32(value, slice_u8.ptr, @intCast(slice_u8.len))))); } + +pub fn CompressionStream(comptime T: type) type { + return struct { + pub fn write(this: *T, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSC.JSValue { + const arguments = callframe.argumentsUndef(7).slice(); + + if (arguments.len != 7) { + globalThis.ERR_MISSING_ARGS("write(flush, in, in_off, in_len, out, out_off, out_len)", .{}).throw(); + return .zero; + } + + var in_off: u32 = 0; + var in_len: u32 = 0; + var out_off: u32 = 0; + var out_len: u32 = 0; + var flush: u32 = 0; + var in: ?[]const u8 = null; + var out: ?[]u8 = null; + + bun.assert(!arguments[0].isUndefined()); // must provide flush value + flush = arguments[0].toU32(); + _ = std.meta.intToEnum(bun.zlib.FlushValue, flush) catch bun.assert(false); // Invalid flush value + + if (arguments[1].isNull()) { + // just a flush + in = null; + in_len = 0; + in_off = 0; + } else { + const in_buf = arguments[1].asArrayBuffer(globalThis).?; + in_off = arguments[2].toU32(); + in_len = arguments[3].toU32(); + bun.assert(in_buf.byte_len >= in_off + in_len); + in = in_buf.byteSlice()[in_off..][0..in_len]; + } + + const out_buf = arguments[4].asArrayBuffer(globalThis).?; + out_off = arguments[5].toU32(); + out_len = arguments[6].toU32(); + bun.assert(out_buf.byte_len >= out_off + out_len); + out = out_buf.byteSlice()[out_off..][0..out_len]; + + bun.assert(!this.write_in_progress); + bun.assert(!this.pending_close); + this.write_in_progress = true; + this.ref(); + + this.stream.setBuffers(in, out); + this.stream.setFlush(@intCast(flush)); + + // + + const vm = globalThis.bunVM(); + var task = AsyncJob.new(.{ + .binding = this, + }); + this.poll_ref.ref(vm); + JSC.WorkPool.schedule(&task.task); + + return .undefined; + } + + const AsyncJob = struct { + task: JSC.WorkPoolTask = .{ .callback = &runTask }, + binding: *T, + + pub usingnamespace bun.New(@This()); + + pub fn runTask(this: *JSC.WorkPoolTask) void { + var job: *AsyncJob = @fieldParentPtr("task", this); + job.run(); + job.destroy(); + } + + pub fn run(job: *AsyncJob) void { + const this = job.binding; + const globalThis: *JSC.JSGlobalObject = this.globalThis; + const vm = globalThis.bunVMConcurrently(); + + this.stream.doWork(); + + this.poll_ref.refConcurrently(vm); + vm.enqueueTaskConcurrent(JSC.ConcurrentTask.create(JSC.Task.init(this))); + } + }; + + pub fn runFromJSThread(this: *T) void { + const globalThis: *JSC.JSGlobalObject = this.globalThis; + const vm = globalThis.bunVM(); + this.poll_ref.unref(vm); + defer this.deref(); + + this.write_in_progress = false; + + if (!(this.checkError(globalThis) catch return globalThis.reportActiveExceptionAsUnhandled(error.JSError))) { + return; + } + + this.stream.updateWriteResult(&this.write_result.?[1], &this.write_result.?[0]); + + _ = this.write_callback.get().?.call(globalThis, this.this_value.get().?, &.{}) catch |err| globalThis.reportActiveExceptionAsUnhandled(err); + + if (this.pending_close) _ = this._close(); + } + + pub fn writeSync(this: *T, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSC.JSValue { + const arguments = callframe.argumentsUndef(7).slice(); + + if (arguments.len != 7) { + globalThis.ERR_MISSING_ARGS("writeSync(flush, in, in_off, in_len, out, out_off, out_len)", .{}).throw(); + return .zero; + } + + var in_off: u32 = 0; + var in_len: u32 = 0; + var out_off: u32 = 0; + var out_len: u32 = 0; + var flush: u32 = 0; + var in: ?[]const u8 = null; + var out: ?[]u8 = null; + + bun.assert(!arguments[0].isUndefined()); // must provide flush value + flush = arguments[0].toU32(); + _ = std.meta.intToEnum(bun.zlib.FlushValue, flush) catch bun.assert(false); // Invalid flush value + + if (arguments[1].isNull()) { + // just a flush + in = null; + in_len = 0; + in_off = 0; + } else { + const in_buf = arguments[1].asArrayBuffer(globalThis).?; + in_off = arguments[2].toU32(); + in_len = arguments[3].toU32(); + bun.assert(in_buf.byte_len >= in_off + in_len); + in = in_buf.byteSlice()[in_off..][0..in_len]; + } + + const out_buf = arguments[4].asArrayBuffer(globalThis).?; + out_off = arguments[5].toU32(); + out_len = arguments[6].toU32(); + bun.assert(out_buf.byte_len >= out_off + out_len); + out = out_buf.byteSlice()[out_off..][0..out_len]; + + bun.assert(!this.write_in_progress); + bun.assert(!this.pending_close); + this.write_in_progress = true; + this.ref(); + + this.stream.setBuffers(in, out); + this.stream.setFlush(@intCast(flush)); + + // + + this.stream.doWork(); + if (this.checkError(globalThis) catch return .zero) { + this.stream.updateWriteResult(&this.write_result.?[1], &this.write_result.?[0]); + this.write_in_progress = false; + } + this.deref(); + + return .undefined; + } + + pub fn reset(this: *T, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSC.JSValue { + _ = callframe; + + const err = this.stream.reset(); + if (err.isError()) { + this.emitError(globalThis, err) catch return .zero; + } + return .undefined; + } + + pub fn close(this: *T, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSC.JSValue { + _ = globalThis; + _ = callframe; + this._close(); + return .undefined; + } + + fn _close(this: *T) void { + if (this.write_in_progress) { + this.pending_close = true; + return; + } + this.pending_close = false; + this.closed = true; + this.this_value.deinit(); + this.stream.close(); + } + + pub fn setOnError(this: *T, globalThis: *JSC.JSGlobalObject, value: JSC.JSValue) bool { + if (value.isFunction()) { + this.onerror_value.set(globalThis, value); + } + return true; + } + + pub fn getOnError(this: *T, globalThis: *JSC.JSGlobalObject) JSC.JSValue { + _ = globalThis; + return this.onerror_value.get() orelse .undefined; + } + + /// returns true if no error was detected/emitted + fn checkError(this: *T, globalThis: *JSC.JSGlobalObject) !bool { + const err = this.stream.getErrorInfo(); + if (!err.isError()) return true; + try this.emitError(globalThis, err); + return false; + } + + fn emitError(this: *T, globalThis: *JSC.JSGlobalObject, err_: Error) !void { + var msg_str = bun.String.createFormat("{s}", .{std.mem.sliceTo(err_.msg, 0) orelse ""}) catch bun.outOfMemory(); + const msg_value = msg_str.transferToJS(globalThis); + const err_value = JSC.jsNumber(err_.err); + var code_str = bun.String.createFormat("{s}", .{std.mem.sliceTo(err_.code, 0) orelse ""}) catch bun.outOfMemory(); + const code_value = code_str.transferToJS(globalThis); + + _ = try this.onerror_value.get().?.call(globalThis, this.this_value.get().?, &.{ msg_value, err_value, code_value }); + + this.write_in_progress = false; + if (this.pending_close) _ = this._close(); + } + + pub fn finalize(this: *T) void { + this.deref(); + } + }; +} + +pub const NativeZlib = JSC.Codegen.JSNativeZlib.getConstructor; + +pub const SNativeZlib = struct { + pub usingnamespace bun.NewRefCounted(@This(), deinit); + pub usingnamespace JSC.Codegen.JSNativeZlib; + pub usingnamespace CompressionStream(@This()); + + ref_count: u32 = 1, + mode: bun.zlib.NodeMode, + globalThis: *JSC.JSGlobalObject, + stream: ZlibContext = .{}, + write_result: ?[*]u32 = null, + write_callback: JSC.Strong = .{}, + onerror_value: JSC.Strong = .{}, + poll_ref: bun.Async.KeepAlive = .{}, + this_value: JSC.Strong = .{}, + write_in_progress: bool = false, + pending_close: bool = false, + closed: bool = false, + + pub fn constructor(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) ?*@This() { + const arguments = callframe.argumentsUndef(4).ptr; + + var mode = arguments[0]; + if (!mode.isNumber()) { + _ = globalThis.throwInvalidArgumentTypeValue("mode", "number", mode); + return null; + } + const mode_double = mode.asNumber(); + if (@mod(mode_double, 1.0) != 0.0) { + _ = globalThis.throwInvalidArgumentTypeValue("mode", "integer", mode); + return null; + } + const mode_int: i64 = @intFromFloat(mode_double); + if (mode_int < 1 or mode_int > 7) { + _ = globalThis.throwRangeError(mode_int, .{ .field_name = "mode", .min = 1, .max = 7 }); + return null; + } + + const ptr = SNativeZlib.new(.{ + .mode = @enumFromInt(mode_int), + .globalThis = globalThis, + }); + ptr.stream.mode = ptr.mode; + return ptr; + } + + //// adding this didnt help much but leaving it here to compare the number with later + // pub fn estimatedSize(this: *const SNativeZlib) usize { + // _ = this; + // const internal_state_size = 3309; // @sizeOf(@cImport(@cInclude("deflate.h")).internal_state) @ cloudflare/zlib @ 92530568d2c128b4432467b76a3b54d93d6350bd + // return @sizeOf(SNativeZlib) + internal_state_size; + // } + + pub fn init(this: *SNativeZlib, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSC.JSValue { + const arguments = callframe.argumentsUndef(7).slice(); + + if (arguments.len != 7) { + globalThis.ERR_MISSING_ARGS("init(windowBits, level, memLevel, strategy, writeResult, writeCallback, dictionary)", .{}).throw(); + return .zero; + } + + const windowBits = validators.validateInt32(globalThis, arguments[0], "windowBits", .{}, null, null) catch return .zero; + const level = validators.validateInt32(globalThis, arguments[1], "level", .{}, null, null) catch return .zero; + const memLevel = validators.validateInt32(globalThis, arguments[2], "memLevel", .{}, null, null) catch return .zero; + const strategy = validators.validateInt32(globalThis, arguments[3], "strategy", .{}, null, null) catch return .zero; + // this does not get gc'd because it is stored in the JS object's `this._writeState`. and the JS object is tied to the native handle as `_handle[owner_symbol]`. + const writeResult = arguments[4].asArrayBuffer(globalThis).?.asU32().ptr; + const writeCallback = validators.validateFunction(globalThis, arguments[5], "writeCallback", .{}) catch return .zero; + const dictionary = if (arguments[6].isUndefined()) null else arguments[6].asArrayBuffer(globalThis).?.byteSlice(); + + this.write_result = writeResult; + this.write_callback.set(globalThis, writeCallback); + + this.stream.init(level, windowBits, memLevel, strategy, dictionary); + + return .undefined; + } + + pub fn params(this: *SNativeZlib, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSC.JSValue { + const arguments = callframe.argumentsUndef(2).slice(); + + if (arguments.len != 2) { + globalThis.ERR_MISSING_ARGS("params(level, strategy)", .{}).throw(); + return .zero; + } + + const level = validators.validateInt32(globalThis, arguments[0], "level", .{}, null, null) catch return .zero; + const strategy = validators.validateInt32(globalThis, arguments[1], "strategy", .{}, null, null) catch return .zero; + + const err = this.stream.setParams(level, strategy); + if (err.isError()) { + this.emitError(globalThis, err) catch return .zero; + } + return .undefined; + } + + pub fn deinit(this: *@This()) void { + this.write_callback.deinit(); + this.onerror_value.deinit(); + this.destroy(); + } +}; + +const Error = struct { + msg: ?[*:0]const u8, + err: c_int, + code: ?[*:0]const u8, + + pub const ok: Error = init(null, 0, null); + + pub fn init(msg: ?[*:0]const u8, err: c_int, code: ?[*:0]const u8) Error { + return .{ + .msg = msg, + .err = err, + .code = code, + }; + } + + pub fn isError(this: Error) bool { + return this.msg != null; + } +}; + +const ZlibContext = struct { + const c = bun.zlib; + const GZIP_HEADER_ID1: u8 = 0x1f; + const GZIP_HEADER_ID2: u8 = 0x8b; + + mode: c.NodeMode = .NONE, + state: c.z_stream = std.mem.zeroes(c.z_stream), + err: c.ReturnCode = .Ok, + flush: c.FlushValue = .NoFlush, + dictionary: []const u8 = "", + gzip_id_bytes_read: u8 = 0, + + pub fn init(this: *ZlibContext, level: c_int, windowBits: c_int, memLevel: c_int, strategy: c_int, dictionary: ?[]const u8) void { + this.flush = .NoFlush; + this.err = .Ok; + + const windowBitsActual = switch (this.mode) { + .NONE => unreachable, + .DEFLATE, .INFLATE => windowBits, + .GZIP, .GUNZIP => windowBits + 16, + .UNZIP => windowBits + 32, + .DEFLATERAW, .INFLATERAW => windowBits * -1, + .BROTLI_DECODE, .BROTLI_ENCODE => unreachable, + }; + + this.dictionary = dictionary orelse ""; + + switch (this.mode) { + .NONE => unreachable, + .DEFLATE, .GZIP, .DEFLATERAW => this.err = c.deflateInit2_(&this.state, level, 8, windowBitsActual, memLevel, strategy, c.zlibVersion(), @sizeOf(c.z_stream)), + .INFLATE, .GUNZIP, .UNZIP, .INFLATERAW => this.err = c.inflateInit2_(&this.state, windowBitsActual, c.zlibVersion(), @sizeOf(c.z_stream)), + .BROTLI_DECODE => @panic("TODO"), + .BROTLI_ENCODE => @panic("TODO"), + } + if (this.err != .Ok) { + this.mode = .NONE; + return; + } + + _ = this.setDictionary(); + } + + pub fn setDictionary(this: *ZlibContext) Error { + const dict = this.dictionary; + if (dict.len == 0) return Error.ok; + this.err = .Ok; + switch (this.mode) { + .DEFLATE, .DEFLATERAW => { + this.err = c.deflateSetDictionary(&this.state, dict.ptr, @intCast(dict.len)); + }, + .INFLATERAW => { + this.err = c.inflateSetDictionary(&this.state, dict.ptr, @intCast(dict.len)); + }, + else => {}, + } + if (this.err != .Ok) { + return this.error_for_message("Failed to set dictionary"); + } + return Error.ok; + } + + pub fn setParams(this: *ZlibContext, level: c_int, strategy: c_int) Error { + this.err = .Ok; + switch (this.mode) { + .DEFLATE, .DEFLATERAW => { + this.err = c.deflateParams(&this.state, level, strategy); + }, + else => {}, + } + if (this.err != .Ok and this.err != .BufError) { + return this.error_for_message("Failed to set parameters"); + } + return Error.ok; + } + + fn error_for_message(this: *ZlibContext, default: [*:0]const u8) Error { + var message = default; + if (this.state.err_msg) |msg| message = msg; + return .{ + .msg = message, + .err = @intFromEnum(this.err), + .code = @tagName(this.err), + }; + } + + pub fn reset(this: *ZlibContext) Error { + this.err = .Ok; + switch (this.mode) { + .DEFLATE, .DEFLATERAW, .GZIP => { + this.err = c.deflateReset(&this.state); + }, + .INFLATE, .INFLATERAW, .GUNZIP => { + this.err = c.inflateReset(&this.state); + }, + else => {}, + } + if (this.err != .Ok) { + return this.error_for_message("Failed to reset stream"); + } + return this.setDictionary(); + } + + pub fn setBuffers(this: *ZlibContext, in: ?[]const u8, out: ?[]u8) void { + this.state.avail_in = if (in) |p| @intCast(p.len) else 0; + this.state.next_in = if (in) |p| p.ptr else null; + this.state.avail_out = if (out) |p| @intCast(p.len) else 0; + this.state.next_out = if (out) |p| p.ptr else null; + } + + pub fn setFlush(this: *ZlibContext, flush: c_int) void { + this.flush = @enumFromInt(flush); + } + + pub fn doWork(this: *ZlibContext) void { + var next_expected_header_byte: ?[*]const u8 = null; + + // If the avail_out is left at 0, then it means that it ran out + // of room. If there was avail_out left over, then it means + // that all of the input was consumed. + switch (this.mode) { + .DEFLATE, .GZIP, .DEFLATERAW => { + return this.doWorkDeflate(); + }, + .UNZIP => { + if (this.state.avail_in > 0) { + next_expected_header_byte = this.state.next_in.?; + } + if (this.gzip_id_bytes_read == 0) { + if (next_expected_header_byte == null) { + return this.doWorkInflate(); + } + if (next_expected_header_byte.?[0] == GZIP_HEADER_ID1) { + this.gzip_id_bytes_read = 1; + next_expected_header_byte.? += 1; + if (this.state.avail_in == 1) { // The only available byte was already read. + return this.doWorkInflate(); + } + } else { + this.mode = .INFLATE; + return this.doWorkInflate(); + } + } + if (this.gzip_id_bytes_read == 1) { + if (next_expected_header_byte == null) { + return this.doWorkInflate(); + } + if (next_expected_header_byte.?[0] == GZIP_HEADER_ID2) { + this.gzip_id_bytes_read = 2; + this.mode = .GUNZIP; + } else { + this.mode = .INFLATE; + } + return this.doWorkInflate(); + } + bun.assert(false); // invalid number of gzip magic number bytes read + }, + .INFLATE, .GUNZIP, .INFLATERAW => { + return this.doWorkInflate(); + }, + .NONE => {}, + .BROTLI_ENCODE, .BROTLI_DECODE => {}, + } + } + + fn doWorkDeflate(this: *ZlibContext) void { + this.err = c.deflate(&this.state, this.flush); + } + + fn doWorkInflate(this: *ZlibContext) void { + this.err = c.inflate(&this.state, this.flush); + + if (this.mode != .INFLATERAW and this.err == .NeedDict and this.dictionary.len > 0) { + this.err = c.inflateSetDictionary(&this.state, this.dictionary.ptr, @intCast(this.dictionary.len)); + + if (this.err == .Ok) { + this.err = c.inflate(&this.state, this.flush); + } else if (this.err == .DataError) { + this.err = .NeedDict; + } + } + while (this.state.avail_in > 0 and this.mode == .GUNZIP and this.err == .StreamEnd and this.state.next_in.?[0] != 0) { + // Bytes remain in input buffer. Perhaps this is another compressed member in the same archive, or just trailing garbage. + // Trailing zero bytes are okay, though, since they are frequently used for padding. + _ = this.reset(); + this.err = c.inflate(&this.state, this.flush); + } + } + + pub fn updateWriteResult(this: *ZlibContext, avail_in: *u32, avail_out: *u32) void { + avail_in.* = this.state.avail_in; + avail_out.* = this.state.avail_out; + } + + pub fn getErrorInfo(this: *ZlibContext) Error { + switch (this.err) { + .Ok, .BufError => { + if (this.state.avail_out != 0 and this.flush == .Finish) { + return this.error_for_message("unexpected end of file"); + } + }, + .StreamEnd => {}, + .NeedDict => { + if (this.dictionary.len == 0) { + return this.error_for_message("Missing dictionary"); + } else { + return this.error_for_message("Bad dictionary"); + } + }, + else => { + return this.error_for_message("Zlib error"); + }, + } + return Error.ok; + } + + pub fn close(this: *ZlibContext) void { + var status = c.ReturnCode.Ok; + switch (this.mode) { + .DEFLATE, .DEFLATERAW, .GZIP => { + status = c.deflateEnd(&this.state); + }, + .INFLATE, .INFLATERAW, .GUNZIP, .UNZIP => { + status = c.inflateEnd(&this.state); + }, + .NONE => {}, + .BROTLI_ENCODE, .BROTLI_DECODE => {}, + } + bun.assert(status == .Ok or status == .DataError); + this.mode = .NONE; + } +}; + +pub const NativeBrotli = JSC.Codegen.JSNativeBrotli.getConstructor; + +pub const SNativeBrotli = struct { + pub usingnamespace bun.NewRefCounted(@This(), deinit); + pub usingnamespace JSC.Codegen.JSNativeZlib; + pub usingnamespace CompressionStream(@This()); + + ref_count: u32 = 1, + mode: bun.zlib.NodeMode, + globalThis: *JSC.JSGlobalObject, + stream: BrotliContext = .{}, + write_result: ?[*]u32 = null, + write_callback: JSC.Strong = .{}, + onerror_value: JSC.Strong = .{}, + poll_ref: bun.Async.KeepAlive = .{}, + this_value: JSC.Strong = .{}, + write_in_progress: bool = false, + pending_close: bool = false, + closed: bool = false, + + pub fn constructor(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) ?*@This() { + const arguments = callframe.argumentsUndef(1).ptr; + + var mode = arguments[0]; + if (!mode.isNumber()) { + _ = globalThis.throwInvalidArgumentTypeValue("mode", "number", mode); + return null; + } + const mode_double = mode.asNumber(); + if (@mod(mode_double, 1.0) != 0.0) { + _ = globalThis.throwInvalidArgumentTypeValue("mode", "integer", mode); + return null; + } + const mode_int: i64 = @intFromFloat(mode_double); + if (mode_int < 8 or mode_int > 9) { + _ = globalThis.throwRangeError(mode_int, .{ .field_name = "mode", .min = 8, .max = 9 }); + return null; + } + + const ptr = @This().new(.{ + .mode = @enumFromInt(mode_int), + .globalThis = globalThis, + }); + ptr.stream.mode = ptr.mode; + ptr.stream.mode_ = ptr.mode; + return ptr; + } + + pub fn estimatedSize(this: *const SNativeBrotli) usize { + const encoder_state_size: usize = 5143; // @sizeOf(@cImport(@cInclude("brotli/encode.h")).BrotliEncoderStateStruct) + const decoder_state_size: usize = 855; // @sizeOf(@cImport(@cInclude("brotli/decode.h")).BrotliDecoderStateStruct) + return @sizeOf(SNativeBrotli) + switch (this.mode) { + .BROTLI_ENCODE => encoder_state_size, + .BROTLI_DECODE => decoder_state_size, + else => 0, + }; + } + + pub fn init(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSC.JSValue { + const arguments = callframe.argumentsUndef(3).slice(); + if (arguments.len != 3) { + globalThis.ERR_MISSING_ARGS("init(params, writeResult, writeCallback)", .{}).throw(); + return .zero; + } + + // this does not get gc'd because it is stored in the JS object's `this._writeState`. and the JS object is tied to the native handle as `_handle[owner_symbol]`. + const writeResult = arguments[1].asArrayBuffer(globalThis).?.asU32().ptr; + const writeCallback = validators.validateFunction(globalThis, arguments[2], "writeCallback", .{}) catch return .zero; + this.write_result = writeResult; + this.write_callback.set(globalThis, writeCallback); + + var err = this.stream.init(); + if (err.isError()) { + this.emitError(globalThis, err) catch return .zero; + return JSC.jsBoolean(false); + } + + const params_ = arguments[0].asArrayBuffer(globalThis).?.asU32(); + + for (params_, 0..) |d, i| { + // (d == -1) { + if (d == std.math.maxInt(u32)) { + continue; + } + err = this.stream.setParams(@intCast(i), d); + if (err.isError()) { + // this.emitError(globalThis, err) catch return .zero; //XXX: onerror isn't set yet + return JSC.jsBoolean(false); + } + } + return JSC.jsBoolean(true); + } + + pub fn params(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) JSC.JSValue { + _ = this; + _ = globalThis; + _ = callframe; + // intentionally left empty + return .undefined; + } + + pub fn deinit(this: *@This()) void { + this.write_callback.deinit(); + this.onerror_value.deinit(); + this.destroy(); + } +}; + +const BrotliContext = struct { + const c = bun.brotli.c; + const Op = bun.brotli.c.BrotliEncoder.Operation; + + mode: bun.zlib.NodeMode = .NONE, + mode_: bun.zlib.NodeMode = .NONE, + state: *anyopaque = undefined, + + next_in: ?[*]const u8 = null, + next_out: ?[*]u8 = null, + avail_in: usize = 0, + avail_out: usize = 0, + + flush: Op = .process, + + last_result: extern union { e: c_int, d: c.BrotliDecoderResult } = @bitCast(@as(u32, 0)), + error_: c.BrotliDecoderErrorCode2 = .NO_ERROR, + + pub fn init(this: *BrotliContext) Error { + switch (this.mode_) { + .BROTLI_ENCODE => { + const alloc = &bun.brotli.BrotliAllocator.alloc; + const free = &bun.brotli.BrotliAllocator.free; + const state = c.BrotliEncoderCreateInstance(alloc, free, null); + if (state == null) { + return Error.init("Could not initialize Brotli instance", -1, "ERR_ZLIB_INITIALIZATION_FAILED"); + } + this.state = @ptrCast(state.?); + return Error.ok; + }, + .BROTLI_DECODE => { + const alloc = &bun.brotli.BrotliAllocator.alloc; + const free = &bun.brotli.BrotliAllocator.free; + const state = c.BrotliDecoderCreateInstance(alloc, free, null); + if (state == null) { + return Error.init("Could not initialize Brotli instance", -1, "ERR_ZLIB_INITIALIZATION_FAILED"); + } + this.state = @ptrCast(state.?); + return Error.ok; + }, + else => unreachable, + } + } + + pub fn setParams(this: *BrotliContext, key: c_uint, value: u32) Error { + switch (this.mode_) { + .BROTLI_ENCODE => { + if (c.BrotliEncoderSetParameter(@ptrCast(this.state), key, value) == 0) { + return Error.init("Setting parameter failed", -1, "ERR_BROTLI_PARAM_SET_FAILED"); + } + return Error.ok; + }, + .BROTLI_DECODE => { + if (c.BrotliDecoderSetParameter(@ptrCast(this.state), key, value) == 0) { + return Error.init("Setting parameter failed", -1, "ERR_BROTLI_PARAM_SET_FAILED"); + } + return Error.ok; + }, + else => unreachable, + } + } + + pub fn reset(this: *BrotliContext) Error { + return this.init(); + } + + pub fn setBuffers(this: *BrotliContext, in: ?[]const u8, out: ?[]u8) void { + this.next_in = if (in) |p| p.ptr else null; + this.next_out = if (out) |p| p.ptr else null; + this.avail_in = if (in) |p| p.len else 0; + this.avail_out = if (out) |p| p.len else 0; + } + + pub fn setFlush(this: *BrotliContext, flush: c_int) void { + this.flush = @enumFromInt(flush); + } + + pub fn doWork(this: *BrotliContext) void { + switch (this.mode_) { + .BROTLI_ENCODE => { + var next_in = this.next_in; + this.last_result.e = c.BrotliEncoderCompressStream(@ptrCast(this.state), this.flush, &this.avail_in, &next_in, &this.avail_out, &this.next_out, null); + this.next_in.? += @intFromPtr(next_in.?) - @intFromPtr(this.next_in.?); + }, + .BROTLI_DECODE => { + var next_in = this.next_in; + this.last_result.d = c.BrotliDecoderDecompressStream(@ptrCast(this.state), &this.avail_in, &next_in, &this.avail_out, &this.next_out, null); + this.next_in.? += @intFromPtr(next_in.?) - @intFromPtr(this.next_in.?); + if (this.last_result.d == .err) { + this.error_ = c.BrotliDecoderGetErrorCode(@ptrCast(this.state)); + } + }, + else => unreachable, + } + } + + pub fn updateWriteResult(this: *BrotliContext, avail_in: *u32, avail_out: *u32) void { + avail_in.* = @intCast(this.avail_in); + avail_out.* = @intCast(this.avail_out); + } + + pub fn getErrorInfo(this: *BrotliContext) Error { + switch (this.mode_) { + .BROTLI_ENCODE => { + if (this.last_result.e == 0) { + return Error.init("Compression failed", -1, "ERR_BROTLI_COMPRESSION_FAILED"); + } + return Error.ok; + }, + .BROTLI_DECODE => { + if (this.error_ != .NO_ERROR) { + return Error.init("Decompression failed", @intFromEnum(this.error_), code_for_error(this.error_)); + } else if (this.flush == .finish and this.last_result.d == .needs_more_input) { + return Error.init("unexpected end of file", @intFromEnum(bun.zlib.ReturnCode.BufError), "Z_BUF_ERROR"); + } + return Error.ok; + }, + else => unreachable, + } + } + + pub fn close(this: *BrotliContext) void { + switch (this.mode_) { + .BROTLI_ENCODE => c.BrotliEncoderDestroyInstance(@ptrCast(@alignCast(this.state))), + .BROTLI_DECODE => c.BrotliDecoderDestroyInstance(@ptrCast(@alignCast(this.state))), + else => unreachable, + } + this.mode = .NONE; + } + + fn code_for_error(err: c.BrotliDecoderErrorCode2) [:0]const u8 { + const E = c.BrotliDecoderErrorCode2; + const names = comptime std.meta.fieldNames(E); + const values = comptime std.enums.values(E); + inline for (names, values) |n, v| { + if (err == v) { + return "ERR_BROTLI_DECODER_" ++ n; + } + } + unreachable; + } +}; diff --git a/src/bun.js/node/util/validators.zig b/src/bun.js/node/util/validators.zig index f13de796c2..b7f56555c0 100644 --- a/src/bun.js/node/util/validators.zig +++ b/src/bun.js/node/util/validators.zig @@ -20,8 +20,7 @@ pub fn throwErrInvalidArgValue( args: anytype, ) !void { @setCold(true); - const err = JSC.toTypeError(.ERR_INVALID_ARG_VALUE, fmt, args, globalThis); - globalThis.vm().throwError(globalThis, err); + globalThis.ERR_INVALID_ARG_VALUE(fmt, args).throw(); return error.InvalidArgument; } @@ -53,19 +52,13 @@ pub fn throwRangeError( args: anytype, ) !void { @setCold(true); - const err = globalThis.createRangeErrorInstanceWithCode(JSC.Node.ErrorCode.ERR_OUT_OF_RANGE, fmt, args); - globalThis.vm().throwError(globalThis, err); + globalThis.ERR_OUT_OF_RANGE(fmt, args).throw(); return error.InvalidArgument; } -/// -(2^53 - 1) -pub const NUMBER__MIN_SAFE_INTEGER: i64 = -9007199254740991; -/// (2^53 – 1) -pub const NUMBER__MAX_SAFE_INTEGER: i64 = 9007199254740991; - pub fn validateInteger(globalThis: *JSGlobalObject, value: JSValue, comptime name_fmt: string, name_args: anytype, min_value: ?i64, max_value: ?i64) !i64 { - const min = min_value orelse NUMBER__MIN_SAFE_INTEGER; - const max = max_value orelse NUMBER__MAX_SAFE_INTEGER; + const min = min_value orelse JSC.MIN_SAFE_INTEGER; + const max = max_value orelse JSC.MAX_SAFE_INTEGER; if (!value.isNumber()) try throwErrInvalidArgType(globalThis, name_fmt, name_args, "number", value); @@ -88,11 +81,13 @@ pub fn validateInt32(globalThis: *JSGlobalObject, value: JSValue, comptime name_ try throwErrInvalidArgType(globalThis, name_fmt, name_args, "number", value); } if (!value.isInt32()) { - try throwRangeError(globalThis, "The value of \"" ++ name_fmt ++ "\" is out of range. It must be an integer. Received {s}", name_args ++ .{value}); + var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis }; + try throwRangeError(globalThis, "The value of \"" ++ name_fmt ++ "\" is out of range. It must be an integer. Received {}", name_args ++ .{value.toFmt(&formatter)}); } const num = value.asInt32(); if (num < min or num > max) { - try throwRangeError(globalThis, "The value of \"" ++ name_fmt ++ "\" is out of range. It must be >= {d} and <= {d}. Received {s}", name_args ++ .{ min, max, value }); + var formatter = JSC.ConsoleObject.Formatter{ .globalThis = globalThis }; + try throwRangeError(globalThis, "The value of \"" ++ name_fmt ++ "\" is out of range. It must be >= {d} and <= {d}. Received {}", name_args ++ .{ min, max, value.toFmt(&formatter) }); } return num; } @@ -219,9 +214,10 @@ pub fn validateBooleanArray(globalThis: *JSGlobalObject, value: JSValue, comptim return i; } -pub fn validateFunction(globalThis: *JSGlobalObject, value: JSValue, comptime name_fmt: string, name_args: anytype) !void { +pub fn validateFunction(globalThis: *JSGlobalObject, value: JSValue, comptime name_fmt: string, name_args: anytype) !JSValue { if (!value.jsType().isFunction()) try throwErrInvalidArgType(globalThis, name_fmt, name_args, "function", value); + return value; } pub fn validateUndefined(globalThis: *JSGlobalObject, value: JSValue, comptime name_fmt: string, name_args: anytype) !void { diff --git a/src/bun.zig b/src/bun.zig index 0da72ab1e1..c15cd38740 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -3451,9 +3451,7 @@ pub fn assert_neql(a: anytype, b: anytype) callconv(callconv_inline) void { } pub fn unsafeAssert(condition: bool) callconv(callconv_inline) void { - if (!condition) { - unreachable; - } + if (!condition) unreachable; } pub const dns = @import("./dns.zig"); diff --git a/src/codegen/class-definitions.ts b/src/codegen/class-definitions.ts index 1e4ef00415..fe5944fce4 100644 --- a/src/codegen/class-definitions.ts +++ b/src/codegen/class-definitions.ts @@ -58,6 +58,7 @@ export interface ClassDefinition { values?: string[]; JSType?: string; noConstructor?: boolean; + wantsThis?: boolean; estimatedSize?: boolean; hasPendingActivity?: boolean; isEventEmitter?: boolean; diff --git a/src/codegen/generate-classes.ts b/src/codegen/generate-classes.ts index 7295986bfd..10c705c219 100644 --- a/src/codegen/generate-classes.ts +++ b/src/codegen/generate-classes.ts @@ -361,6 +361,12 @@ JSC_DECLARE_CUSTOM_GETTER(js${typeName}Constructor); `; } + if (obj.wantsThis) { + externs += ` +extern JSC_CALLCONV void* JSC_HOST_CALL_ATTRIBUTES ${classSymbolName(typeName, "_setThis")}(JSC::JSGlobalObject*, void*, JSC::EncodedJSValue); +`; + } + if (obj.structuredClone) { externs += `extern JSC_CALLCONV void JSC_HOST_CALL_ATTRIBUTES ${symbolName( @@ -645,7 +651,15 @@ JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES ${name}::construct(JSC::JSGlobalObj : "" } - RELEASE_AND_RETURN(scope, JSValue::encode(instance)); + auto value = JSValue::encode(instance); +${ + obj.wantsThis + ? ` + ${classSymbolName(typeName, "_setThis")}(globalObject, ptr, value); +` + : "" +} + RELEASE_AND_RETURN(scope, value); } void ${name}::initializeProperties(VM& vm, JSC::JSGlobalObject* globalObject, ${prototypeName(typeName)}* prototype) @@ -1606,6 +1620,7 @@ function generateZig( construct, finalize, noConstructor = false, + wantsThis = false, overridesToJS = false, estimatedSize, call = false, @@ -1718,6 +1733,16 @@ const JavaScriptCoreBindings = struct { `; } + if (construct && !noConstructor && wantsThis) { + exports.set("_setThis", classSymbolName(typeName, "_setThis")); + output += ` + pub fn ${classSymbolName(typeName, "_setThis")}(globalObject: *JSC.JSGlobalObject, ptr: *anyopaque, this: JSC.JSValue) callconv(JSC.conv) void { + const real: *${typeName} = @ptrCast(@alignCast(ptr)); + real.this_value.set(globalObject, this); + } + `; + } + if (call) { exports.set("call", classSymbolName(typeName, "call")); output += ` diff --git a/src/codegen/generate-node-errors.ts b/src/codegen/generate-node-errors.ts index e8683237f1..41b5de47c7 100644 --- a/src/codegen/generate-node-errors.ts +++ b/src/codegen/generate-node-errors.ts @@ -39,10 +39,8 @@ const std = @import("std"); const bun = @import("root").bun; const JSC = bun.JSC; -fn ErrorBuilder(comptime code_: Error, comptime fmt_: [:0]const u8, Args: type) type { +fn ErrorBuilder(comptime code: Error, comptime fmt: [:0]const u8, Args: type) type { return struct { - const code = code_; - const fmt = fmt_; globalThis: *JSC.JSGlobalObject, args: Args, @@ -60,7 +58,6 @@ fn ErrorBuilder(comptime code_: Error, comptime fmt_: [:0]const u8, Args: type) pub inline fn reject(this: @This()) JSC.JSValue { return JSC.JSPromise.rejectedPromiseValue(this.globalThis, code.fmt(this.globalThis, fmt, this.args)); } - }; } diff --git a/src/deps/brotli_decoder.zig b/src/deps/brotli_decoder.zig index 2d4dad91a4..1634931b54 100644 --- a/src/deps/brotli_decoder.zig +++ b/src/deps/brotli_decoder.zig @@ -12,26 +12,28 @@ pub const BrotliSharedDictionaryType = enum_BrotliSharedDictionaryType; // pub extern fn BrotliSharedDictionaryCreateInstance(alloc_func: brotli_alloc_func, free_func: brotli_free_func, @"opaque": ?*anyopaque) ?*BrotliSharedDictionary; // pub extern fn BrotliSharedDictionaryDestroyInstance(dict: ?*BrotliSharedDictionary) void; // pub extern fn BrotliSharedDictionaryAttach(dict: ?*BrotliSharedDictionary, @"type": BrotliSharedDictionaryType, data_size: usize, data: [*]const u8) c_int; + +pub extern fn BrotliDecoderSetParameter(state: *BrotliDecoder, param: c_uint, value: u32) callconv(.C) c_int; +pub extern fn BrotliDecoderAttachDictionary(state: *BrotliDecoder, @"type": BrotliSharedDictionaryType, data_size: usize, data: [*]const u8) callconv(.C) c_int; +pub extern fn BrotliDecoderCreateInstance(alloc_func: brotli_alloc_func, free_func: brotli_free_func, @"opaque": ?*anyopaque) callconv(.C) ?*BrotliDecoder; +pub extern fn BrotliDecoderDestroyInstance(state: *BrotliDecoder) callconv(.C) void; +pub extern fn BrotliDecoderDecompress(encoded_size: usize, encoded_buffer: [*]const u8, decoded_size: *usize, decoded_buffer: [*]u8) callconv(.C) BrotliDecoderResult; +pub extern fn BrotliDecoderDecompressStream(state: *BrotliDecoder, available_in: *usize, next_in: *?[*]const u8, available_out: *usize, next_out: *?[*]u8, total_out: ?*usize) callconv(.C) BrotliDecoderResult; +pub extern fn BrotliDecoderHasMoreOutput(state: *const BrotliDecoder) callconv(.C) c_int; +pub extern fn BrotliDecoderTakeOutput(state: *BrotliDecoder, size: *usize) callconv(.C) ?[*]const u8; +pub extern fn BrotliDecoderIsUsed(state: *const BrotliDecoder) callconv(.C) c_int; +pub extern fn BrotliDecoderIsFinished(state: *const BrotliDecoder) callconv(.C) c_int; +pub extern fn BrotliDecoderGetErrorCode(state: *const BrotliDecoder) callconv(.C) BrotliDecoderErrorCode2; +pub extern fn BrotliDecoderErrorString(c: BrotliDecoderErrorCode) callconv(.C) ?[*:0]const u8; +pub extern fn BrotliDecoderVersion() callconv(.C) u32; + pub const BrotliDecoder = opaque { - extern "C" fn BrotliDecoderSetParameter(state: *BrotliDecoder, param: BrotliDecoderParameter, value: u32) callconv(.C) c_int; - extern "C" fn BrotliDecoderAttachDictionary(state: *BrotliDecoder, @"type": BrotliSharedDictionaryType, data_size: usize, data: [*]const u8) callconv(.C) c_int; - extern "C" fn BrotliDecoderCreateInstance(alloc_func: brotli_alloc_func, free_func: brotli_free_func, @"opaque": ?*anyopaque) callconv(.C) ?*BrotliDecoder; - extern "C" fn BrotliDecoderDestroyInstance(state: *BrotliDecoder) callconv(.C) void; - extern "C" fn BrotliDecoderDecompress(encoded_size: usize, encoded_buffer: [*]const u8, decoded_size: *usize, decoded_buffer: [*]u8) callconv(.C) BrotliDecoderResult; - extern "C" fn BrotliDecoderDecompressStream(state: *BrotliDecoder, available_in: *usize, next_in: *?[*]const u8, available_out: *usize, next_out: *?[*]u8, total_out: ?*usize) callconv(.C) BrotliDecoderResult; - extern "C" fn BrotliDecoderHasMoreOutput(state: *const BrotliDecoder) callconv(.C) c_int; - extern "C" fn BrotliDecoderTakeOutput(state: *BrotliDecoder, size: *usize) callconv(.C) ?[*]const u8; - extern "C" fn BrotliDecoderIsUsed(state: *const BrotliDecoder) callconv(.C) c_int; - extern "C" fn BrotliDecoderIsFinished(state: *const BrotliDecoder) callconv(.C) c_int; - extern "C" fn BrotliDecoderGetErrorCode(state: *const BrotliDecoder) callconv(.C) BrotliDecoderErrorCode; - extern "C" fn BrotliDecoderErrorString(c: BrotliDecoderErrorCode) callconv(.C) ?[*:0]const u8; - extern "C" fn BrotliDecoderVersion() callconv(.C) u32; const BrotliDecoderSetMetadataCallbacks = fn (state: *BrotliDecoder, start_func: brotli_decoder_metadata_start_func, chunk_func: brotli_decoder_metadata_chunk_func, @"opaque": ?*anyopaque) callconv(.C) void; const brotli_decoder_metadata_start_func = ?*const fn (?*anyopaque, usize) callconv(.C) void; const brotli_decoder_metadata_chunk_func = ?*const fn (?*anyopaque, [*]const u8, usize) callconv(.C) void; pub fn setParameter(state: *BrotliDecoder, param: BrotliDecoderParameter, value: u32) callconv(.C) bool { - return BrotliDecoderSetParameter(state, param, value) > 0; + return BrotliDecoderSetParameter(state, @intFromEnum(param), value) > 0; } pub fn attachDictionary(state: *BrotliDecoder, @"type": BrotliSharedDictionaryType, data: []const u8) callconv(.C) c_int { @@ -73,7 +75,7 @@ pub const BrotliDecoder = opaque { } pub fn getErrorCode(state: *const BrotliDecoder) callconv(.C) BrotliDecoderErrorCode { - return BrotliDecoderGetErrorCode(state); + return @enumFromInt(@intFromEnum(BrotliDecoderGetErrorCode(state))); } pub fn errorString(c: BrotliDecoderErrorCode) callconv(.C) [:0]const u8 { @@ -152,6 +154,38 @@ pub const BrotliDecoderErrorCode = enum(c_int) { ALLOC_BLOCK_TYPE_TREES = -30, UNREACHABLE = -31, }; +pub const BrotliDecoderErrorCode2 = enum(c_int) { + NO_ERROR = 0, + SUCCESS = 1, + NEEDS_MORE_INPUT = 2, + NEEDS_MORE_OUTPUT = 3, + ERROR_FORMAT_EXUBERANT_NIBBLE = -1, + ERROR_FORMAT_RESERVED = -2, + ERROR_FORMAT_EXUBERANT_META_NIBBLE = -3, + ERROR_FORMAT_SIMPLE_HUFFMAN_ALPHABET = -4, + ERROR_FORMAT_SIMPLE_HUFFMAN_SAME = -5, + ERROR_FORMAT_CL_SPACE = -6, + ERROR_FORMAT_HUFFMAN_SPACE = -7, + ERROR_FORMAT_CONTEXT_MAP_REPEAT = -8, + ERROR_FORMAT_BLOCK_LENGTH_1 = -9, + ERROR_FORMAT_BLOCK_LENGTH_2 = -10, + ERROR_FORMAT_TRANSFORM = -11, + ERROR_FORMAT_DICTIONARY = -12, + ERROR_FORMAT_WINDOW_BITS = -13, + ERROR_FORMAT_PADDING_1 = -14, + ERROR_FORMAT_PADDING_2 = -15, + ERROR_FORMAT_DISTANCE = -16, + ERROR_COMPOUND_DICTIONARY = -18, + ERROR_DICTIONARY_NOT_SET = -19, + ERROR_INVALID_ARGUMENTS = -20, + ERROR_ALLOC_CONTEXT_MODES = -21, + ERROR_ALLOC_TREE_GROUPS = -22, + ERROR_ALLOC_CONTEXT_MAP = -25, + ERROR_ALLOC_RING_BUFFER_1 = -26, + ERROR_ALLOC_RING_BUFFER_2 = -27, + ERROR_ALLOC_BLOCK_TYPE_TREES = -30, + ERROR_UNREACHABLE = -31, +}; pub const BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION: c_int = 0; pub const BROTLI_DECODER_PARAM_LARGE_WINDOW: c_int = 1; pub const BrotliDecoderParameter = enum(c_uint) { diff --git a/src/deps/brotli_encoder.zig b/src/deps/brotli_encoder.zig index 9bd8614205..5143169724 100644 --- a/src/deps/brotli_encoder.zig +++ b/src/deps/brotli_encoder.zig @@ -9,6 +9,8 @@ pub const BROTLI_SHARED_DICTIONARY_RAW: c_int = 0; pub const BROTLI_SHARED_DICTIONARY_SERIALIZED: c_int = 1; pub const enum_BrotliSharedDictionaryType = c_uint; pub const BrotliSharedDictionaryType = enum_BrotliSharedDictionaryType; +pub const struct_BrotliEncoderPreparedDictionaryStruct = opaque {}; +pub const BrotliEncoderPreparedDictionary = struct_BrotliEncoderPreparedDictionaryStruct; extern fn BrotliSharedDictionaryCreateInstance(alloc_func: brotli_alloc_func, free_func: brotli_free_func, @"opaque": ?*anyopaque) ?*BrotliSharedDictionary; extern fn BrotliSharedDictionaryDestroyInstance(dict: ?*BrotliSharedDictionary) void; extern fn BrotliSharedDictionaryAttach(dict: ?*BrotliSharedDictionary, @"type": BrotliSharedDictionaryType, data_size: usize, data: [*c]const u8) c_int; @@ -46,7 +48,25 @@ pub const BrotliEncoderParameter = enum(c_uint) { npostfix = 7, ndirect = 8, stream_offset = 9, + // update kMaxBrotliParam in src/js/node/zlib.ts if this list changes }; + +pub extern fn BrotliEncoderSetParameter(state: *BrotliEncoder, param: c_uint, value: u32) c_int; +pub extern fn BrotliEncoderCreateInstance(alloc_func: brotli_alloc_func, free_func: brotli_free_func, @"opaque": ?*anyopaque) ?*BrotliEncoder; +pub extern fn BrotliEncoderDestroyInstance(state: *BrotliEncoder) void; +pub extern fn BrotliEncoderPrepareDictionary(@"type": BrotliSharedDictionaryType, data_size: usize, data: [*c]const u8, quality: c_int, alloc_func: brotli_alloc_func, free_func: brotli_free_func, @"opaque": ?*anyopaque) *BrotliEncoderPreparedDictionary; +pub extern fn BrotliEncoderDestroyPreparedDictionary(dictionary: *BrotliEncoderPreparedDictionary) void; +pub extern fn BrotliEncoderAttachPreparedDictionary(state: *BrotliEncoder, dictionary: ?*const BrotliEncoderPreparedDictionary) c_int; +pub extern fn BrotliEncoderMaxCompressedSize(input_size: usize) usize; +pub extern fn BrotliEncoderCompress(quality: c_int, lgwin: c_int, mode: BrotliEncoderMode, input_size: usize, input_buffer: [*]const u8, encoded_size: *usize, encoded_buffer: [*]u8) c_int; +pub extern fn BrotliEncoderCompressStream(state: *BrotliEncoder, op: BrotliEncoder.Operation, available_in: *usize, next_in: *?[*]const u8, available_out: *usize, next_in: ?*?[*]u8, total_out: ?*usize) c_int; +pub extern fn BrotliEncoderIsFinished(state: *BrotliEncoder) c_int; +pub extern fn BrotliEncoderHasMoreOutput(state: *BrotliEncoder) c_int; +pub extern fn BrotliEncoderTakeOutput(state: *BrotliEncoder, size: *usize) ?[*]const u8; +pub extern fn BrotliEncoderEstimatePeakMemoryUsage(quality: c_int, lgwin: c_int, input_size: usize) usize; +pub extern fn BrotliEncoderGetPreparedDictionarySize(dictionary: ?*const BrotliEncoderPreparedDictionary) usize; +pub extern fn BrotliEncoderVersion() u32; + pub const BrotliEncoder = opaque { pub const Operation = enum(c_uint) { process = 0, @@ -55,24 +75,6 @@ pub const BrotliEncoder = opaque { emit_metadata = 3, }; - extern fn BrotliEncoderSetParameter(state: *BrotliEncoder, param: BrotliEncoderParameter, value: u32) c_int; - extern fn BrotliEncoderCreateInstance(alloc_func: brotli_alloc_func, free_func: brotli_free_func, @"opaque": ?*anyopaque) *BrotliEncoder; - extern fn BrotliEncoderDestroyInstance(state: *BrotliEncoder) void; - pub const struct_BrotliEncoderPreparedDictionaryStruct = opaque {}; - pub const BrotliEncoderPreparedDictionary = struct_BrotliEncoderPreparedDictionaryStruct; - extern fn BrotliEncoderPrepareDictionary(@"type": BrotliSharedDictionaryType, data_size: usize, data: [*c]const u8, quality: c_int, alloc_func: brotli_alloc_func, free_func: brotli_free_func, @"opaque": ?*anyopaque) *BrotliEncoderPreparedDictionary; - extern fn BrotliEncoderDestroyPreparedDictionary(dictionary: *BrotliEncoderPreparedDictionary) void; - extern fn BrotliEncoderAttachPreparedDictionary(state: *BrotliEncoder, dictionary: ?*const BrotliEncoderPreparedDictionary) c_int; - extern fn BrotliEncoderMaxCompressedSize(input_size: usize) usize; - extern fn BrotliEncoderCompress(quality: c_int, lgwin: c_int, mode: BrotliEncoderMode, input_size: usize, input_buffer: [*]const u8, encoded_size: *usize, encoded_buffer: [*]u8) c_int; - extern fn BrotliEncoderCompressStream(state: *BrotliEncoder, op: Operation, available_in: *usize, next_in: *?[*]const u8, available_out: *usize, next_out: ?[*]u8, total_out: ?*usize) c_int; - extern fn BrotliEncoderIsFinished(state: *BrotliEncoder) c_int; - extern fn BrotliEncoderHasMoreOutput(state: *BrotliEncoder) c_int; - extern fn BrotliEncoderTakeOutput(state: *BrotliEncoder, size: *usize) ?[*]const u8; - extern fn BrotliEncoderEstimatePeakMemoryUsage(quality: c_int, lgwin: c_int, input_size: usize) usize; - extern fn BrotliEncoderGetPreparedDictionarySize(dictionary: ?*const BrotliEncoderPreparedDictionary) usize; - extern fn BrotliEncoderVersion() u32; - pub fn createInstance(alloc_func: brotli_alloc_func, free_func: brotli_free_func, @"opaque": ?*anyopaque) callconv(.C) ?*BrotliEncoder { return BrotliEncoderCreateInstance(alloc_func, free_func, @"opaque"); } @@ -121,7 +123,7 @@ pub const BrotliEncoder = opaque { } pub fn setParameter(state: *BrotliEncoder, param: BrotliEncoderParameter, value: u32) bool { - return BrotliEncoderSetParameter(state, param, value) > 0; + return BrotliEncoderSetParameter(state, @intFromEnum(param), value) > 0; } }; diff --git a/src/js/internal/errors.ts b/src/js/internal/errors.ts index dedce8f38a..9486c9095f 100644 --- a/src/js/internal/errors.ts +++ b/src/js/internal/errors.ts @@ -6,4 +6,7 @@ export default { ERR_IPC_CHANNEL_CLOSED: $newCppFunction("ErrorCode.cpp", "jsFunction_ERR_IPC_CHANNEL_CLOSED", 0), ERR_SOCKET_BAD_TYPE: $newCppFunction("ErrorCode.cpp", "jsFunction_ERR_SOCKET_BAD_TYPE", 0), ERR_INVALID_PROTOCOL: $newCppFunction("ErrorCode.cpp", "jsFunction_ERR_INVALID_PROTOCOL", 0), + ERR_BROTLI_INVALID_PARAM: $newCppFunction("ErrorCode.cpp", "jsFunction_ERR_BROTLI_INVALID_PARAM", 0), + ERR_BUFFER_TOO_LARGE: $newCppFunction("ErrorCode.cpp", "jsFunction_ERR_BUFFER_TOO_LARGE", 0), + ERR_ZLIB_INITIALIZATION_FAILED: $newCppFunction("ErrorCode.cpp", "jsFunction_ERR_ZLIB_INITIALIZATION_FAILED", 0), }; diff --git a/src/js/internal/validators.ts b/src/js/internal/validators.ts new file mode 100644 index 0000000000..a626c360d1 --- /dev/null +++ b/src/js/internal/validators.ts @@ -0,0 +1,8 @@ +export default { + validateInteger: $newCppFunction("NodeValidator.cpp", "jsFunction_validateInteger", 0), + validateNumber: $newCppFunction("NodeValidator.cpp", "jsFunction_validateNumber", 0), + validateString: $newCppFunction("NodeValidator.cpp", "jsFunction_validateString", 0), + validateFiniteNumber: $newCppFunction("NodeValidator.cpp", "jsFunction_validateFiniteNumber", 0), + checkRangesOrGetDefault: $newCppFunction("NodeValidator.cpp", "jsFunction_checkRangesOrGetDefault", 0), + validateFunction: $newCppFunction("NodeValidator.cpp", "jsFunction_validateFunction", 0), +}; diff --git a/src/js/node/zlib.ts b/src/js/node/zlib.ts index 72f5e5ed70..ef8f56317b 100644 --- a/src/js/node/zlib.ts +++ b/src/js/node/zlib.ts @@ -1,340 +1,61 @@ // Hardcoded module "node:zlib" const assert = require("node:assert"); -const stream = require("node:stream"); const BufferModule = require("node:buffer"); -const { ERR_INVALID_ARG_TYPE } = require("internal/errors"); +const crc32 = $newZigFunction("node_zlib_binding.zig", "crc32", 1); +const NativeZlib = $zig("node_zlib_binding.zig", "NativeZlib"); +const NativeBrotli = $zig("node_zlib_binding.zig", "NativeBrotli"); + +const ObjectKeys = Object.keys; +const ArrayPrototypePush = Array.prototype.push; const ObjectDefineProperty = Object.defineProperty; +const ObjectDefineProperties = Object.defineProperties; +const ObjectFreeze = Object.freeze; +const StringPrototypeStartsWith = String.prototype.startsWith; +const MathMax = Math.max; +const ArrayPrototypeMap = Array.prototype.map; +const TypedArrayPrototypeFill = Uint8Array.prototype.fill; +const ArrayPrototypeForEach = Array.prototype.forEach; +const NumberIsNaN = Number.isNaN; -const createBrotliEncoder = $newZigFunction("node_zlib_binding.zig", "createBrotliEncoder", 3); -const createBrotliDecoder = $newZigFunction("node_zlib_binding.zig", "createBrotliDecoder", 3); -const createZlibEncoder = $newZigFunction("node_zlib_binding.zig", "createZlibEncoder", 3); -const createZlibDecoder = $newZigFunction("node_zlib_binding.zig", "createZlibDecoder", 3); +const ArrayBufferIsView = ArrayBuffer.isView; +const isArrayBufferView = ArrayBufferIsView; +const isAnyArrayBuffer = b => b instanceof ArrayBuffer || b instanceof SharedArrayBuffer; +const kMaxLength = $requireMap.$get("buffer")?.exports.kMaxLength ?? BufferModule.kMaxLength; -const maxOutputLengthDefault = $requireMap.$get("buffer")?.exports.kMaxLength ?? BufferModule.kMaxLength; +const { + ERR_BROTLI_INVALID_PARAM, + ERR_BUFFER_TOO_LARGE, + ERR_INVALID_ARG_TYPE, + ERR_OUT_OF_RANGE, + ERR_ZLIB_INITIALIZATION_FAILED, +} = require("internal/errors"); +const { Transform, finished } = require("node:stream"); +const owner_symbol = Symbol("owner_symbol"); +const { + checkRangesOrGetDefault, + validateFunction, + validateUint32, + validateFiniteNumber, +} = require("internal/validators"); -// - -const kHandle = Symbol("kHandle"); const kFlushFlag = Symbol("kFlushFlag"); -const kFlushBuffers: Buffer[] = []; -{ - const dummyArrayBuffer = new ArrayBuffer(); - for (const flushFlag of [0, 1, 2, 3, 4, 5]) { - kFlushBuffers[flushFlag] = Buffer.from(dummyArrayBuffer); - kFlushBuffers[flushFlag][kFlushFlag] = flushFlag; - } -} +const kError = Symbol("kError"); -// TODO: this doesn't match node exactly so improve this more later -const alias = function (proto, to, from) { - ObjectDefineProperty(proto, to, { - get: function () { - return this[kHandle][from]; - }, - set: function (v) {}, // changing these would be a bug - enumerable: true, - }); -}; - -// - -function ZlibBase(options, method) { - if (options == null) options = {}; - if ($isObject(options)) { - options.maxOutputLength ??= maxOutputLengthDefault; - - if (options.encoding || options.objectMode || options.writableObjectMode) { - options = { ...options }; - options.encoding = null; - options.objectMode = false; - options.writableObjectMode = false; - } - } - const [, , private_constructor] = methods[method]; - this[kHandle] = private_constructor(options, {}, null, method); - stream.Transform.$call(this, options); -} -ZlibBase.prototype = Object.create(stream.Transform.prototype); -ObjectDefineProperty(ZlibBase.prototype, "_handle", { - get: function () { - return this[kHandle]; - }, - set: function (newval) { - //noop - }, -}); -alias(ZlibBase.prototype, "bytesWritten", "bytesWritten"); -alias(ZlibBase.prototype, "bytesRead", "bytesRead"); -alias(ZlibBase.prototype, "_closed", "closed"); -alias(ZlibBase.prototype, "_chunkSize", "chunkSize"); -alias(ZlibBase.prototype, "_defaultFlushFlag", "flush"); -alias(ZlibBase.prototype, "_finishFlushFlag", "finishFlush"); -alias(ZlibBase.prototype, "_defaultFullFlushFlag", "fullFlush"); -alias(ZlibBase.prototype, "_maxOutputLength", "maxOutputLength"); -ZlibBase.prototype.flush = function (kind, callback) { - if (typeof kind === "function" || (kind === undefined && !callback)) { - callback = kind; - kind = this._defaultFullFlushFlag; - } - if (this.writableFinished) { - if (callback) process.nextTick(callback); - } else if (this.writableEnded) { - if (callback) this.once("end", callback); - } else { - this.write(kFlushBuffers[kind], "", callback); - } -}; -ZlibBase.prototype.reset = function () { - assert(this[kHandle], "zlib binding closed"); - return this[kHandle].reset(); -}; -ZlibBase.prototype.close = function (callback) { - if (callback) stream.finished(this, callback); - this.destroy(); -}; -ZlibBase.prototype._transform = function _transform(chunk, encoding, callback) { - try { - callback(undefined, this[kHandle].transformSync(chunk, encoding, false)); - } catch (err) { - callback(err, undefined); - } -}; -ZlibBase.prototype._flush = function _flush(callback) { - try { - callback(undefined, this[kHandle].transformSync("", undefined, true)); - } catch (err) { - callback(err, undefined); - } -}; -ZlibBase.prototype._final = function (callback) { - callback(); -}; -ZlibBase.prototype._processChunk = function (chunk, flushFlag, cb) { - // _processChunk() is left for backwards compatibility - if (typeof cb === "function") processChunk(this, chunk, flushFlag, cb); - else return processChunkSync(this, chunk, flushFlag); -}; - -function processChunkSync(self, chunk, flushFlag) { - return self[kHandle].transformSync(chunk, undefined, false, flushFlag); -} - -function processChunk(self, chunk, flushFlag, cb) { - if (self._closed) return process.nextTick(cb); - self[kHandle].transformSync(chunk, undefined, false, flushFlag); -} - -// - -function Zlib(options, method) { - ZlibBase.$call(this, options, method); -} -Zlib.prototype = Object.create(ZlibBase.prototype); -alias(Zlib.prototype, "_level", "level"); -alias(Zlib.prototype, "_strategy", "strategy"); -Zlib.prototype.params = function (level, strategy, callback) { - return this[kHandle].params(level, strategy, callback); -}; -Zlib.prototype._transform = function _transform(chunk, encoding, callback) { - try { - this[kHandle].transformWith(chunk, encoding, this, false); - callback(); - } catch (err) { - callback(err, undefined); - } -}; - -// - -function BrotliCompress(opts) { - if (!(this instanceof BrotliCompress)) return new BrotliCompress(opts); - ZlibBase.$call(this, opts, BROTLI_ENCODE); -} -BrotliCompress.prototype = Object.create(ZlibBase.prototype); - -// - -function BrotliDecompress(opts) { - if (!(this instanceof BrotliDecompress)) return new BrotliDecompress(opts); - ZlibBase.$call(this, opts, BROTLI_DECODE); -} -BrotliDecompress.prototype = Object.create(ZlibBase.prototype); - -// - -function Deflate(opts) { - if (!(this instanceof Deflate)) return new Deflate(opts); - Zlib.$call(this, opts, DEFLATE); -} -Deflate.prototype = Object.create(Zlib.prototype); - -// - -function Inflate(opts) { - if (!(this instanceof Inflate)) return new Inflate(opts); - Zlib.$call(this, opts, INFLATE); -} -Inflate.prototype = Object.create(Zlib.prototype); - -// - -function DeflateRaw(opts) { - if (!(this instanceof DeflateRaw)) return new DeflateRaw(opts); - Zlib.$call(this, opts, DEFLATERAW); -} -DeflateRaw.prototype = Object.create(Zlib.prototype); - -// - -function InflateRaw(opts) { - if (!(this instanceof InflateRaw)) return new InflateRaw(opts); - Zlib.$call(this, opts, INFLATERAW); -} -InflateRaw.prototype = Object.create(Zlib.prototype); - -// - -function Gzip(opts) { - if (!(this instanceof Gzip)) return new Gzip(opts); - Zlib.$call(this, opts, GZIP); -} -Gzip.prototype = Object.create(Zlib.prototype); - -// - -function Gunzip(opts) { - if (!(this instanceof Gunzip)) return new Gunzip(opts); - Zlib.$call(this, opts, GUNZIP); -} -Gunzip.prototype = Object.create(Zlib.prototype); - -// - -function Unzip(opts) { - if (!(this instanceof Unzip)) return new Unzip(opts); - Zlib.$call(this, opts, UNZIP); -} -Unzip.prototype = Object.create(Zlib.prototype); - -// - -const constants = { - Z_NO_FLUSH: 0, - Z_PARTIAL_FLUSH: 1, - Z_SYNC_FLUSH: 2, - Z_FULL_FLUSH: 3, - Z_FINISH: 4, - Z_BLOCK: 5, - Z_TREES: 6, - Z_OK: 0, - Z_STREAM_END: 1, - Z_NEED_DICT: 2, - Z_ERRNO: -1, - Z_STREAM_ERROR: -2, - Z_DATA_ERROR: -3, - Z_MEM_ERROR: -4, - Z_BUF_ERROR: -5, - Z_VERSION_ERROR: -6, - Z_NO_COMPRESSION: 0, - Z_BEST_SPEED: 1, - Z_BEST_COMPRESSION: 9, - Z_DEFAULT_COMPRESSION: -1, - Z_FILTERED: 1, - Z_HUFFMAN_ONLY: 2, - Z_RLE: 3, - Z_FIXED: 4, - Z_DEFAULT_STRATEGY: 0, - Z_BINARY: 0, - Z_TEXT: 1, - Z_ASCII: 1, - Z_UNKNOWN: 2, - Z_DEFLATED: 8, - DEFLATE: 1, - INFLATE: 2, - GZIP: 3, - GUNZIP: 4, - DEFLATERAW: 5, - INFLATERAW: 6, - UNZIP: 7, - BROTLI_DECODE: 8, - BROTLI_ENCODE: 9, - Z_MIN_WINDOWBITS: 8, - Z_MAX_WINDOWBITS: 15, - Z_DEFAULT_WINDOWBITS: 15, - Z_MIN_CHUNK: 64, - Z_MAX_CHUNK: Infinity, - Z_DEFAULT_CHUNK: 16384, - Z_MIN_MEMLEVEL: 1, - Z_MAX_MEMLEVEL: 9, - Z_DEFAULT_MEMLEVEL: 8, - Z_MIN_LEVEL: -1, - Z_MAX_LEVEL: 9, - Z_DEFAULT_LEVEL: -1, - BROTLI_OPERATION_PROCESS: 0, - BROTLI_OPERATION_FLUSH: 1, - BROTLI_OPERATION_FINISH: 2, - BROTLI_OPERATION_EMIT_METADATA: 3, - BROTLI_PARAM_MODE: 0, - BROTLI_MODE_GENERIC: 0, - BROTLI_MODE_TEXT: 1, - BROTLI_MODE_FONT: 2, - BROTLI_DEFAULT_MODE: 0, - BROTLI_PARAM_QUALITY: 1, - BROTLI_MIN_QUALITY: 0, - BROTLI_MAX_QUALITY: 11, - BROTLI_DEFAULT_QUALITY: 11, - BROTLI_PARAM_LGWIN: 2, - BROTLI_MIN_WINDOW_BITS: 10, - BROTLI_MAX_WINDOW_BITS: 24, - BROTLI_LARGE_MAX_WINDOW_BITS: 30, - BROTLI_DEFAULT_WINDOW: 22, - BROTLI_PARAM_LGBLOCK: 3, - BROTLI_MIN_INPUT_BLOCK_BITS: 16, - BROTLI_MAX_INPUT_BLOCK_BITS: 24, - BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING: 4, - BROTLI_PARAM_SIZE_HINT: 5, - BROTLI_PARAM_LARGE_WINDOW: 6, - BROTLI_PARAM_NPOSTFIX: 7, - BROTLI_PARAM_NDIRECT: 8, - BROTLI_DECODER_RESULT_ERROR: 0, - BROTLI_DECODER_RESULT_SUCCESS: 1, - BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT: 2, - BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT: 3, - BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION: 0, - BROTLI_DECODER_PARAM_LARGE_WINDOW: 1, - BROTLI_DECODER_NO_ERROR: 0, - BROTLI_DECODER_SUCCESS: 1, - BROTLI_DECODER_NEEDS_MORE_INPUT: 2, - BROTLI_DECODER_NEEDS_MORE_OUTPUT: 3, - BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_NIBBLE: -1, - BROTLI_DECODER_ERROR_FORMAT_RESERVED: -2, - BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_META_NIBBLE: -3, - BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_ALPHABET: -4, - BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_SAME: -5, - BROTLI_DECODER_ERROR_FORMAT_CL_SPACE: -6, - BROTLI_DECODER_ERROR_FORMAT_HUFFMAN_SPACE: -7, - BROTLI_DECODER_ERROR_FORMAT_CONTEXT_MAP_REPEAT: -8, - BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_1: -9, - BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_2: -10, - BROTLI_DECODER_ERROR_FORMAT_TRANSFORM: -11, - BROTLI_DECODER_ERROR_FORMAT_DICTIONARY: -12, - BROTLI_DECODER_ERROR_FORMAT_WINDOW_BITS: -13, - BROTLI_DECODER_ERROR_FORMAT_PADDING_1: -14, - BROTLI_DECODER_ERROR_FORMAT_PADDING_2: -15, - BROTLI_DECODER_ERROR_FORMAT_DISTANCE: -16, - BROTLI_DECODER_ERROR_DICTIONARY_NOT_SET: -19, - BROTLI_DECODER_ERROR_INVALID_ARGUMENTS: -20, - BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MODES: -21, - BROTLI_DECODER_ERROR_ALLOC_TREE_GROUPS: -22, - BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MAP: -25, - BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_1: -26, - BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_2: -27, - BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES: -30, - BROTLI_DECODER_ERROR_UNREACHABLE: -31, -}; -const { DEFLATE, INFLATE, GZIP, GUNZIP, DEFLATERAW, INFLATERAW, UNZIP, BROTLI_DECODE, BROTLI_ENCODE } = constants; +const { zlib: constants } = process.binding("constants"); +// prettier-ignore +const { + // Zlib flush levels + Z_NO_FLUSH, Z_BLOCK, Z_PARTIAL_FLUSH, Z_SYNC_FLUSH, Z_FULL_FLUSH, Z_FINISH, + // Zlib option values + Z_MIN_CHUNK, Z_MIN_WINDOWBITS, Z_MAX_WINDOWBITS, Z_MIN_LEVEL, Z_MAX_LEVEL, Z_MIN_MEMLEVEL, Z_MAX_MEMLEVEL, + Z_DEFAULT_CHUNK, Z_DEFAULT_COMPRESSION, Z_DEFAULT_STRATEGY, Z_DEFAULT_WINDOWBITS, Z_DEFAULT_MEMLEVEL, Z_FIXED, + // Node's compression stream modes (node_zlib_mode) + DEFLATE, DEFLATERAW, INFLATE, INFLATERAW, GZIP, GUNZIP, UNZIP, BROTLI_DECODE, BROTLI_ENCODE, + // Brotli operations (~flush levels) + BROTLI_OPERATION_PROCESS, BROTLI_OPERATION_FLUSH, BROTLI_OPERATION_FINISH, BROTLI_OPERATION_EMIT_METADATA, +} = constants; // Translation table for return codes. const codes = { @@ -348,96 +69,670 @@ const codes = { Z_BUF_ERROR: constants.Z_BUF_ERROR, Z_VERSION_ERROR: constants.Z_VERSION_ERROR, }; - -for (const ckey of Object.keys(codes)) { +for (const ckey of ObjectKeys(codes)) { codes[codes[ckey]] = ckey; } -const methods = [ - [], - [Deflate, true, createZlibEncoder], - [Inflate, false, createZlibDecoder], - [Gzip, true, createZlibEncoder], - [Gunzip, false, createZlibDecoder], - [DeflateRaw, true, createZlibEncoder], - [InflateRaw, false, createZlibDecoder], - [Unzip, false, createZlibDecoder], - [BrotliDecompress, false, createBrotliDecoder], - [BrotliCompress, true, createBrotliEncoder], -] as const; +function zlibBuffer(engine, buffer, callback) { + validateFunction(callback, "callback"); + // Streams do not support non-Uint8Array ArrayBufferViews yet. Convert it to a Buffer without copying. + if (isArrayBufferView(buffer)) { + buffer = Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength); + } else if (isAnyArrayBuffer(buffer)) { + buffer = Buffer.from(buffer); + } + engine.buffers = null; + engine.nread = 0; + engine.cb = callback; + engine.on("data", zlibBufferOnData); + engine.on("error", zlibBufferOnError); + engine.on("end", zlibBufferOnEnd); + engine.end(buffer); +} -function createConvenienceMethod(method: number, is_sync: boolean) { - const [, , private_constructor] = methods[method]; - - switch (is_sync) { - case false: - return function (buffer, options, callback) { - if (typeof options === "function") { - callback = options; - options = {}; - } - if (options == null) options = {}; - if ($isObject(options)) options.maxOutputLength ??= maxOutputLengthDefault; - if (typeof callback !== "function") throw ERR_INVALID_ARG_TYPE("callback", "function", callback); - const coder = private_constructor(options, {}, callback, method); - coder.transform(buffer, undefined, true); - }; - case true: - return function (buffer, options) { - if (options == null) options = {}; - if ($isObject(options)) options.maxOutputLength ??= maxOutputLengthDefault; - const coder = private_constructor(options, {}, null, method); - return coder.transformSync(buffer, undefined, true); - }; +function zlibBufferOnData(chunk) { + if (!this.buffers) this.buffers = [chunk]; + else ArrayPrototypePush.$call(this.buffers, chunk); + this.nread += chunk.length; + if (this.nread > this._maxOutputLength) { + this.close(); + this.removeAllListeners("end"); + this.cb(ERR_BUFFER_TOO_LARGE(this._maxOutputLength)); } } -function createCreator(method: number) { - const Constructor = methods[method][0]; - return function (opts) { - return new Constructor(opts); +function zlibBufferOnError(err) { + this.removeAllListeners("end"); + this.cb(err); +} + +function zlibBufferOnEnd() { + let buf; + if (this.nread === 0) { + buf = Buffer.alloc(0); + } else { + const bufs = this.buffers; + buf = bufs.length === 1 ? bufs[0] : Buffer.concat(bufs, this.nread); + } + this.close(); + if (this._info) this.cb(null, { buffer: buf, engine: this }); + else this.cb(null, buf); +} + +function zlibBufferSync(engine, buffer) { + if (typeof buffer === "string") { + buffer = Buffer.from(buffer); + } else if (!isArrayBufferView(buffer)) { + if (isAnyArrayBuffer(buffer)) { + buffer = Buffer.from(buffer); + } else { + throw ERR_INVALID_ARG_TYPE("buffer", "string, Buffer, TypedArray, DataView, or ArrayBuffer", buffer); + } + } + buffer = processChunkSync(engine, buffer, engine._finishFlushFlag); + if (engine._info) return { buffer, engine }; + return buffer; +} + +function zlibOnError(message, errno, code) { + const self = this[owner_symbol]; + // There is no way to cleanly recover. Continuing only obscures problems. + const error = new Error(message); + error.errno = errno; + error.code = code; + self.destroy(error); + self[kError] = error; +} + +const FLUSH_BOUND = [ + [Z_NO_FLUSH, Z_BLOCK], + [BROTLI_OPERATION_PROCESS, BROTLI_OPERATION_EMIT_METADATA], +]; +const FLUSH_BOUND_IDX_NORMAL = 0; +const FLUSH_BOUND_IDX_BROTLI = 1; + +// The base class for all Zlib-style streams. +function ZlibBase(opts, mode, handle, { flush, finishFlush, fullFlush }) { + let chunkSize = Z_DEFAULT_CHUNK; + let maxOutputLength = kMaxLength; + // 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 <= BROTLI_ENCODE); + + let flushBoundIdx; + if (mode !== BROTLI_ENCODE && mode !== BROTLI_DECODE) { + flushBoundIdx = FLUSH_BOUND_IDX_NORMAL; + } else { + flushBoundIdx = FLUSH_BOUND_IDX_BROTLI; + } + + if (opts) { + chunkSize = opts.chunkSize; + if (!validateFiniteNumber(chunkSize, "options.chunkSize")) { + chunkSize = Z_DEFAULT_CHUNK; + } else if (chunkSize < Z_MIN_CHUNK) { + throw ERR_OUT_OF_RANGE("options.chunkSize", `>= ${Z_MIN_CHUNK}`, chunkSize); + } + + // prettier-ignore + flush = checkRangesOrGetDefault(opts.flush, "options.flush", FLUSH_BOUND[flushBoundIdx][0], FLUSH_BOUND[flushBoundIdx][1], flush); + // prettier-ignore + finishFlush = checkRangesOrGetDefault(opts.finishFlush, "options.finishFlush", FLUSH_BOUND[flushBoundIdx][0], FLUSH_BOUND[flushBoundIdx][1], finishFlush); + // prettier-ignore + maxOutputLength = checkRangesOrGetDefault(opts.maxOutputLength, "options.maxOutputLength", 1, kMaxLength, kMaxLength); + + if (opts.encoding || opts.objectMode || opts.writableObjectMode) { + opts = { ...opts }; + opts.encoding = null; + opts.objectMode = false; + opts.writableObjectMode = false; + } + } + + Transform.$apply(this, [{ autoDestroy: true, ...opts }]); + this[kError] = null; + this.bytesWritten = 0; + 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; +} +ZlibBase.prototype = Object.create(Transform.prototype); + +ObjectDefineProperty(ZlibBase.prototype, "_closed", { + configurable: true, + enumerable: true, + get() { + return !this._handle; + }, +}); + +// `bytesRead` made sense as a name when looking from the zlib engine's +// perspective, but it is inconsistent with all other streams exposed by Node.js +// that have this concept, where it stands for the number of bytes read +// *from* the stream (that is, net.Socket/tls.Socket & file system streams). +ObjectDefineProperty(ZlibBase.prototype, "bytesRead", { + configurable: true, + get: function () { + return this.bytesWritten; + }, + set: function (value) { + this.bytesWritten = value; + }, +}); + +ZlibBase.prototype.reset = function () { + assert(this._handle, "zlib binding closed"); + return this._handle.reset(); +}; + +// This is the _flush function called by the transform class, internally, when the last chunk has been written. +ZlibBase.prototype._flush = function (callback) { + this._transform(Buffer.alloc(0), "", callback); +}; + +// Force Transform compat behavior. +ZlibBase.prototype._final = function (callback) { + callback(); +}; + +// 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 = [Z_NO_FLUSH, Z_BLOCK, Z_PARTIAL_FLUSH, Z_SYNC_FLUSH, Z_FULL_FLUSH, Z_FINISH]; +for (let i = 0; i < kFlushFlagList.length; i++) { + flushiness[kFlushFlagList[i]] = i; +} + +function maxFlush(a, b) { + return flushiness[a] > flushiness[b] ? a : b; +} + +// 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: (typeof Buffer)[] = []; +{ + const dummyArrayBuffer = new ArrayBuffer(); + for (const flushFlag of kFlushFlagList) { + kFlushBuffers[flushFlag] = Buffer.from(dummyArrayBuffer); + kFlushBuffers[flushFlag][kFlushFlag] = flushFlag; + } +} + +ZlibBase.prototype.flush = function (kind, callback) { + if (typeof kind === "function" || (kind === undefined && !callback)) { + callback = kind; + kind = this._defaultFullFlushFlag; + } + if (this.writableFinished) { + if (callback) process.nextTick(callback); + } else if (this.writableEnded) { + if (callback) this.once("end", callback); + } else { + this.write(kFlushBuffers[kind], "", callback); + } +}; + +ZlibBase.prototype.close = function (callback) { + if (callback) finished(this, callback); + this.destroy(); +}; + +ZlibBase.prototype._destroy = function (err, callback) { + _close(this); + callback(err); +}; + +ZlibBase.prototype._transform = function (chunk, encoding, cb) { + 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); +}; + +ZlibBase.prototype._processChunk = function (chunk, flushFlag, cb) { + // _processChunk() is left for backwards compatibility + if (typeof cb === "function") processChunk(this, chunk, flushFlag, cb); + else return processChunkSync(this, chunk, flushFlag); +}; + +function processChunkSync(self, chunk, flushFlag) { + let availInBefore = chunk.byteLength; + let availOutBefore = self._chunkSize - self._outOffset; + let inOff = 0; + let availOutAfter; + let availInAfter; + + const buffers = []; + let nread = 0; + let inputRead = 0; + const state = self._writeState; + const handle = self._handle; + let buffer = self._outBuffer; + let offset = self._outOffset; + const chunkSize = self._chunkSize; + + let error; + self.on("error", function onError(er) { + error = er; + }); + + while (true) { + handle.writeSync( + flushFlag, + chunk, // in + inOff, // in_off + availInBefore, // in_len + buffer, // out + offset, // out_off + availOutBefore, // out_len + ); + if (error) throw error; + else if (self[kError]) throw self[kError]; + + availOutAfter = state[0]; + availInAfter = state[1]; + + const inDelta = availInBefore - availInAfter; + inputRead += inDelta; + + const have = availOutBefore - availOutAfter; + if (have > 0) { + const out = buffer.slice(offset, offset + have); + offset += have; + ArrayPrototypePush.$call(buffers, out); + nread += out.byteLength; + + if (nread > self._maxOutputLength) { + _close(self); + throw ERR_BUFFER_TOO_LARGE(self._maxOutputLength); + } + } else { + assert(have === 0, "have should not go down"); + } + + // Exhausted the output buffer, or used all the input create a new one. + if (availOutAfter === 0 || offset >= chunkSize) { + availOutBefore = chunkSize; + offset = 0; + buffer = Buffer.allocUnsafe(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. + inOff += inDelta; + availInBefore = availInAfter; + } else { + break; + } + } + + self.bytesWritten = inputRead; + _close(self); + + if (nread === 0) return Buffer.alloc(0); + + return buffers.length === 1 ? buffers[0] : Buffer.concat(buffers, nread); +} + +function processChunk(self, chunk, flushFlag, cb) { + const handle = self._handle; + if (!handle) return process.nextTick(cb); + + handle.buffer = chunk; + handle.cb = cb; + handle.availOutBefore = self._chunkSize - self._outOffset; + handle.availInBefore = chunk.byteLength; + handle.inOff = 0; + handle.flushFlag = flushFlag; + + handle.write( + flushFlag, // flush + chunk, // in + 0, // in_off + handle.availInBefore, // in_len + self._outBuffer, // out + self._outOffset, // out_off + handle.availOutBefore, // out_len + ); +} + +function processCallback() { + // 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, // flush + 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, // flush + 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(); +} + +function _close(engine) { + // Caller may invoke .close after a zlib error (which will null _handle) + engine._handle?.close(); + engine._handle = null; +} + +const zlibDefaultOpts = { + flush: Z_NO_FLUSH, + finishFlush: Z_FINISH, + fullFlush: Z_FULL_FLUSH, +}; +// Base class for all streams actually backed by zlib and using zlib-specific +// parameters. +function Zlib(opts, mode) { + let windowBits = Z_DEFAULT_WINDOWBITS; + let level = Z_DEFAULT_COMPRESSION; + let memLevel = Z_DEFAULT_MEMLEVEL; + let strategy = Z_DEFAULT_STRATEGY; + let dictionary; + + if (opts) { + // windowBits is special. On the compression side, 0 is an invalid value. + // But on the decompression side, a value of 0 for windowBits tells zlib + // to use the window size in the zlib header of the compressed stream. + if ((opts.windowBits == null || opts.windowBits === 0) && (mode === INFLATE || mode === GUNZIP || mode === UNZIP)) { + windowBits = 0; + } else { + // `{ windowBits: 8 }` is valid for deflate but not gzip. + const min = Z_MIN_WINDOWBITS + (mode === GZIP ? 1 : 0); + windowBits = checkRangesOrGetDefault( + opts.windowBits, + "options.windowBits", + min, + Z_MAX_WINDOWBITS, + Z_DEFAULT_WINDOWBITS, + ); + } + + level = checkRangesOrGetDefault(opts.level, "options.level", Z_MIN_LEVEL, Z_MAX_LEVEL, Z_DEFAULT_COMPRESSION); + // prettier-ignore + memLevel = checkRangesOrGetDefault(opts.memLevel, "options.memLevel", Z_MIN_MEMLEVEL, Z_MAX_MEMLEVEL, Z_DEFAULT_MEMLEVEL); + // prettier-ignore + strategy = checkRangesOrGetDefault(opts.strategy, "options.strategy", Z_DEFAULT_STRATEGY, Z_FIXED, Z_DEFAULT_STRATEGY); + + dictionary = opts.dictionary; + if (dictionary !== undefined && !isArrayBufferView(dictionary)) { + if (isAnyArrayBuffer(dictionary)) { + dictionary = Buffer.from(dictionary); + } else { + throw ERR_INVALID_ARG_TYPE("options.dictionary", "Buffer, TypedArray, DataView, or ArrayBuffer", dictionary); + } + } + } + + const handle = new NativeZlib(mode); + this._writeState = new Uint32Array(2); + handle.init(windowBits, level, memLevel, strategy, this._writeState, processCallback, dictionary); + + ZlibBase.$apply(this, [opts, mode, handle, zlibDefaultOpts]); + + this._level = level; + this._strategy = strategy; +} +Zlib.prototype = Object.create(ZlibBase.prototype); + +// 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. +function paramsAfterFlushCallback(level, strategy, callback) { + assert(this._handle, "zlib binding closed"); + this._handle.params(level, strategy); + if (!this.destroyed) { + this._level = level; + this._strategy = strategy; + if (callback) callback(); + } +} + +Zlib.prototype.params = function params(level, strategy, callback) { + checkRangesOrGetDefault(level, "level", Z_MIN_LEVEL, Z_MAX_LEVEL); + checkRangesOrGetDefault(strategy, "strategy", Z_DEFAULT_STRATEGY, Z_FIXED); + + if (this._level !== level || this._strategy !== strategy) { + this.flush(Z_SYNC_FLUSH, paramsAfterFlushCallback.bind(this, level, strategy, callback)); + } else { + process.nextTick(callback); + } +}; + +function Deflate(opts) { + if (!(this instanceof Deflate)) return new Deflate(opts); + Zlib.$apply(this, [opts, DEFLATE]); +} +Deflate.prototype = Object.create(Zlib.prototype); + +function Inflate(opts) { + if (!(this instanceof Inflate)) return new Inflate(opts); + Zlib.$apply(this, [opts, INFLATE]); +} +Inflate.prototype = Object.create(Zlib.prototype); + +function Gzip(opts) { + if (!(this instanceof Gzip)) return new Gzip(opts); + Zlib.$apply(this, [opts, GZIP]); +} +Gzip.prototype = Object.create(Zlib.prototype); + +function Gunzip(opts) { + if (!(this instanceof Gunzip)) return new Gunzip(opts); + Zlib.$apply(this, [opts, GUNZIP]); +} +Gunzip.prototype = Object.create(Zlib.prototype); + +function DeflateRaw(opts) { + if (opts && opts.windowBits === 8) opts.windowBits = 9; + if (!(this instanceof DeflateRaw)) return new DeflateRaw(opts); + Zlib.$apply(this, [opts, DEFLATERAW]); +} +DeflateRaw.prototype = Object.create(Zlib.prototype); + +function InflateRaw(opts) { + if (!(this instanceof InflateRaw)) return new InflateRaw(opts); + Zlib.$apply(this, [opts, INFLATERAW]); +} +InflateRaw.prototype = Object.create(Zlib.prototype); + +function Unzip(opts) { + if (!(this instanceof Unzip)) return new Unzip(opts); + Zlib.$apply(this, [opts, UNZIP]); +} +Unzip.prototype = Object.create(Zlib.prototype); + +function createConvenienceMethod(ctor, sync) { + if (sync) { + return function syncBufferWrapper(buffer, opts) { + return zlibBufferSync(new ctor(opts), buffer); + }; + } + return function asyncBufferWrapper(buffer, opts, callback) { + if (typeof opts === "function") { + callback = opts; + opts = {}; + } + return zlibBuffer(new ctor(opts), buffer, callback); }; } -const functions = { - crc32: $newZigFunction("node_zlib_binding.zig", "crc32", 1), +const kMaxBrotliParam = 9; - deflate: createConvenienceMethod(DEFLATE, false), - deflateSync: createConvenienceMethod(DEFLATE, true), - gzip: createConvenienceMethod(GZIP, false), - gzipSync: createConvenienceMethod(GZIP, true), - deflateRaw: createConvenienceMethod(DEFLATERAW, false), - deflateRawSync: createConvenienceMethod(DEFLATERAW, true), - unzip: createConvenienceMethod(UNZIP, false), - unzipSync: createConvenienceMethod(UNZIP, true), - inflate: createConvenienceMethod(INFLATE, false), - inflateSync: createConvenienceMethod(INFLATE, true), - gunzip: createConvenienceMethod(GUNZIP, false), - gunzipSync: createConvenienceMethod(GUNZIP, true), - inflateRaw: createConvenienceMethod(INFLATERAW, false), - inflateRawSync: createConvenienceMethod(INFLATERAW, true), - brotliCompress: createConvenienceMethod(BROTLI_ENCODE, false), - brotliCompressSync: createConvenienceMethod(BROTLI_ENCODE, true), - brotliDecompress: createConvenienceMethod(BROTLI_DECODE, false), - brotliDecompressSync: createConvenienceMethod(BROTLI_DECODE, true), +const brotliInitParamsArray = new Uint32Array(kMaxBrotliParam + 1); - createDeflate: createCreator(DEFLATE), - createInflate: createCreator(INFLATE), - createDeflateRaw: createCreator(DEFLATERAW), - createInflateRaw: createCreator(INFLATERAW), - createGzip: createCreator(GZIP), - createGunzip: createCreator(GUNZIP), - createUnzip: createCreator(UNZIP), - createBrotliCompress: createCreator(BROTLI_ENCODE), - createBrotliDecompress: createCreator(BROTLI_DECODE), +const brotliDefaultOpts = { + flush: BROTLI_OPERATION_PROCESS, + finishFlush: BROTLI_OPERATION_FINISH, + fullFlush: BROTLI_OPERATION_FLUSH, }; -for (const f in functions) { - Object.defineProperty(functions[f], "name", { - value: f, - }); +function Brotli(opts, mode) { + assert(mode === BROTLI_DECODE || mode === BROTLI_ENCODE); + + TypedArrayPrototypeFill.$call(brotliInitParamsArray, -1); + if (opts?.params) { + ArrayPrototypeForEach.$call(ObjectKeys(opts.params), origKey => { + const key = +origKey; + if (NumberIsNaN(key) || key < 0 || key > kMaxBrotliParam || (brotliInitParamsArray[key] | 0) !== -1) { + throw ERR_BROTLI_INVALID_PARAM(origKey); + } + + const value = opts.params[origKey]; + if (typeof value !== "number" && typeof value !== "boolean") { + throw ERR_INVALID_ARG_TYPE("options.params[key]", "number", opts.params[origKey]); + } + brotliInitParamsArray[key] = value; + }); + } + + const handle = new NativeBrotli(mode); + + this._writeState = new Uint32Array(2); + if (!handle.init(brotliInitParamsArray, this._writeState, processCallback)) { + throw ERR_ZLIB_INITIALIZATION_FAILED(); + } + + ZlibBase.$apply(this, [opts, mode, handle, brotliDefaultOpts]); +} +Brotli.prototype = Object.create(Zlib.prototype); + +function BrotliCompress(opts) { + if (!(this instanceof BrotliCompress)) return new BrotliCompress(opts); + Brotli.$apply(this, [opts, BROTLI_ENCODE]); +} +BrotliCompress.prototype = Object.create(Brotli.prototype); + +function BrotliDecompress(opts) { + if (!(this instanceof BrotliDecompress)) return new BrotliDecompress(opts); + Brotli.$apply(this, [opts, BROTLI_DECODE]); +} +BrotliDecompress.prototype = Object.create(Brotli.prototype); + +function createProperty(ctor) { + return { + configurable: true, + enumerable: true, + value: function (options) { + return new ctor(options); + }, + }; } +// Legacy alias on the C++ wrapper object. +ObjectDefineProperty(NativeZlib.prototype, "jsref", { + __proto__: null, + get() { + return this[owner_symbol]; + }, + set(v) { + return (this[owner_symbol] = v); + }, +}); + const zlib = { + crc32, Deflate, Inflate, Gzip, @@ -448,19 +743,95 @@ const zlib = { BrotliCompress, BrotliDecompress, - ...functions, + deflate: createConvenienceMethod(Deflate, false), + deflateSync: createConvenienceMethod(Deflate, true), + gzip: createConvenienceMethod(Gzip, false), + gzipSync: createConvenienceMethod(Gzip, true), + deflateRaw: createConvenienceMethod(DeflateRaw, false), + deflateRawSync: createConvenienceMethod(DeflateRaw, true), + unzip: createConvenienceMethod(Unzip, false), + unzipSync: createConvenienceMethod(Unzip, true), + inflate: createConvenienceMethod(Inflate, false), + inflateSync: createConvenienceMethod(Inflate, true), + gunzip: createConvenienceMethod(Gunzip, false), + gunzipSync: createConvenienceMethod(Gunzip, true), + inflateRaw: createConvenienceMethod(InflateRaw, false), + inflateRawSync: createConvenienceMethod(InflateRaw, true), + brotliCompress: createConvenienceMethod(BrotliCompress, false), + brotliCompressSync: createConvenienceMethod(BrotliCompress, true), + brotliDecompress: createConvenienceMethod(BrotliDecompress, false), + brotliDecompressSync: createConvenienceMethod(BrotliDecompress, true), }; -Object.defineProperty(zlib, "constants", { - writable: false, - configurable: false, - enumerable: true, - value: Object.freeze(constants), -}); -Object.defineProperty(zlib, "codes", { - writable: false, - configurable: false, - enumerable: true, - value: Object.freeze(codes), + +ObjectDefineProperties(zlib, { + createDeflate: createProperty(Deflate), + createInflate: createProperty(Inflate), + createDeflateRaw: createProperty(DeflateRaw), + createInflateRaw: createProperty(InflateRaw), + createGzip: createProperty(Gzip), + createGunzip: createProperty(Gunzip), + createUnzip: createProperty(Unzip), + createBrotliCompress: createProperty(BrotliCompress), + createBrotliDecompress: createProperty(BrotliDecompress), + constants: { + enumerable: true, + value: ObjectFreeze(constants), + }, + codes: { + enumerable: true, + value: ObjectFreeze(codes), + }, }); +// These should be considered deprecated +// expose all the zlib constants +{ + // prettier-ignore + const { Z_OK, Z_STREAM_END, Z_NEED_DICT, Z_ERRNO, Z_STREAM_ERROR, Z_DATA_ERROR, Z_MEM_ERROR, Z_BUF_ERROR, Z_VERSION_ERROR, Z_NO_COMPRESSION, Z_BEST_SPEED, Z_BEST_COMPRESSION, Z_DEFAULT_COMPRESSION, Z_FILTERED, Z_HUFFMAN_ONLY, Z_RLE, ZLIB_VERNUM, Z_MAX_CHUNK, Z_DEFAULT_LEVEL } = constants; + ObjectDefineProperty(zlib, "Z_NO_FLUSH", { value: Z_NO_FLUSH }); + ObjectDefineProperty(zlib, "Z_PARTIAL_FLUSH", { value: Z_PARTIAL_FLUSH }); + ObjectDefineProperty(zlib, "Z_SYNC_FLUSH", { value: Z_SYNC_FLUSH }); + ObjectDefineProperty(zlib, "Z_FULL_FLUSH", { value: Z_FULL_FLUSH }); + ObjectDefineProperty(zlib, "Z_FINISH", { value: Z_FINISH }); + ObjectDefineProperty(zlib, "Z_BLOCK", { value: Z_BLOCK }); + ObjectDefineProperty(zlib, "Z_OK", { value: Z_OK }); + ObjectDefineProperty(zlib, "Z_STREAM_END", { value: Z_STREAM_END }); + ObjectDefineProperty(zlib, "Z_NEED_DICT", { value: Z_NEED_DICT }); + ObjectDefineProperty(zlib, "Z_ERRNO", { value: Z_ERRNO }); + ObjectDefineProperty(zlib, "Z_STREAM_ERROR", { value: Z_STREAM_ERROR }); + ObjectDefineProperty(zlib, "Z_DATA_ERROR", { value: Z_DATA_ERROR }); + ObjectDefineProperty(zlib, "Z_MEM_ERROR", { value: Z_MEM_ERROR }); + ObjectDefineProperty(zlib, "Z_BUF_ERROR", { value: Z_BUF_ERROR }); + ObjectDefineProperty(zlib, "Z_VERSION_ERROR", { value: Z_VERSION_ERROR }); + ObjectDefineProperty(zlib, "Z_NO_COMPRESSION", { value: Z_NO_COMPRESSION }); + ObjectDefineProperty(zlib, "Z_BEST_SPEED", { value: Z_BEST_SPEED }); + ObjectDefineProperty(zlib, "Z_BEST_COMPRESSION", { value: Z_BEST_COMPRESSION }); + ObjectDefineProperty(zlib, "Z_DEFAULT_COMPRESSION", { value: Z_DEFAULT_COMPRESSION }); + ObjectDefineProperty(zlib, "Z_FILTERED", { value: Z_FILTERED }); + ObjectDefineProperty(zlib, "Z_HUFFMAN_ONLY", { value: Z_HUFFMAN_ONLY }); + ObjectDefineProperty(zlib, "Z_RLE", { value: Z_RLE }); + ObjectDefineProperty(zlib, "Z_FIXED", { value: Z_FIXED }); + ObjectDefineProperty(zlib, "Z_DEFAULT_STRATEGY", { value: Z_DEFAULT_STRATEGY }); + ObjectDefineProperty(zlib, "ZLIB_VERNUM", { value: ZLIB_VERNUM }); + ObjectDefineProperty(zlib, "DEFLATE", { value: DEFLATE }); + ObjectDefineProperty(zlib, "INFLATE", { value: INFLATE }); + ObjectDefineProperty(zlib, "GZIP", { value: GZIP }); + ObjectDefineProperty(zlib, "GUNZIP", { value: GUNZIP }); + ObjectDefineProperty(zlib, "DEFLATERAW", { value: DEFLATERAW }); + ObjectDefineProperty(zlib, "INFLATERAW", { value: INFLATERAW }); + ObjectDefineProperty(zlib, "UNZIP", { value: UNZIP }); + ObjectDefineProperty(zlib, "Z_MIN_WINDOWBITS", { value: Z_MIN_WINDOWBITS }); + ObjectDefineProperty(zlib, "Z_MAX_WINDOWBITS", { value: Z_MAX_WINDOWBITS }); + ObjectDefineProperty(zlib, "Z_DEFAULT_WINDOWBITS", { value: Z_DEFAULT_WINDOWBITS }); + ObjectDefineProperty(zlib, "Z_MIN_CHUNK", { value: Z_MIN_CHUNK }); + ObjectDefineProperty(zlib, "Z_MAX_CHUNK", { value: Z_MAX_CHUNK }); + ObjectDefineProperty(zlib, "Z_DEFAULT_CHUNK", { value: Z_DEFAULT_CHUNK }); + ObjectDefineProperty(zlib, "Z_MIN_MEMLEVEL", { value: Z_MIN_MEMLEVEL }); + ObjectDefineProperty(zlib, "Z_MAX_MEMLEVEL", { value: Z_MAX_MEMLEVEL }); + ObjectDefineProperty(zlib, "Z_DEFAULT_MEMLEVEL", { value: Z_DEFAULT_MEMLEVEL }); + ObjectDefineProperty(zlib, "Z_MIN_LEVEL", { value: Z_MIN_LEVEL }); + ObjectDefineProperty(zlib, "Z_MAX_LEVEL", { value: Z_MAX_LEVEL }); + ObjectDefineProperty(zlib, "Z_DEFAULT_LEVEL", { value: Z_DEFAULT_LEVEL }); +} + export default zlib; diff --git a/src/jsc.zig b/src/jsc.zig index c49313cb80..8e4c807264 100644 --- a/src/jsc.zig +++ b/src/jsc.zig @@ -49,10 +49,8 @@ pub const API = struct { pub const UDPSocket = @import("./bun.js/api/bun/udp_socket.zig").UDPSocket; pub const Listener = @import("./bun.js/api/bun/socket.zig").Listener; pub const H2FrameParser = @import("./bun.js/api/bun/h2_frame_parser.zig").H2FrameParser; - pub const BrotliEncoder = @import("./bun.js/api/js_brotli.zig").BrotliEncoder; - pub const BrotliDecoder = @import("./bun.js/api/js_brotli.zig").BrotliDecoder; - pub const ZlibEncoder = @import("./bun.js/api/js_zlib.zig").ZlibEncoder; - pub const ZlibDecoder = @import("./bun.js/api/js_zlib.zig").ZlibDecoder; + pub const NativeZlib = @import("./bun.js/node/node_zlib_binding.zig").SNativeZlib; + pub const NativeBrotli = @import("./bun.js/node/node_zlib_binding.zig").SNativeBrotli; }; pub const Postgres = @import("./sql/postgres.zig"); pub const DNS = @import("./bun.js/api/bun/dns_resolver.zig"); @@ -117,7 +115,6 @@ else pub const Error = @import("ErrorCode").Error; -pub const MAX_SAFE_INTEGER = std.math.maxInt(i52); -pub const MIN_SAFE_INTEGER = std.math.minInt(i52); -pub const MAX_NUMBER = std.math.maxInt(f64); -pub const MIN_NUMBER = std.math.minInt(f64); +pub const MAX_SAFE_INTEGER = 9007199254740991; + +pub const MIN_SAFE_INTEGER = -9007199254740991; diff --git a/src/string.zig b/src/string.zig index 63db7a55ec..742dfd7eea 100644 --- a/src/string.zig +++ b/src/string.zig @@ -742,6 +742,7 @@ pub const String = extern struct { len: usize, ) JSC.JSValue; + /// calls toJS on all elements of `array`. pub fn toJSArray(globalObject: *bun.JSC.JSGlobalObject, array: []const bun.String) JSC.JSValue { JSC.markBinding(@src()); diff --git a/src/zlib.zig b/src/zlib.zig index b141b9137b..537d047709 100644 --- a/src/zlib.zig +++ b/src/zlib.zig @@ -66,7 +66,7 @@ pub const z_streamp = @import("zlib-internal").z_streamp; // } z_stream; const DataType = @import("zlib-internal").DataType; -const FlushValue = @import("zlib-internal").FlushValue; +pub const FlushValue = @import("zlib-internal").FlushValue; pub const ReturnCode = @import("zlib-internal").ReturnCode; // ZEXTERN int ZEXPORT inflateInit OF((z_streamp strm)); @@ -659,7 +659,7 @@ pub extern fn deflate(strm: z_streamp, flush: FlushValue) ReturnCode; /// deallocated). pub extern fn deflateEnd(stream: z_streamp) ReturnCode; -pub extern fn deflateReset(stream: z_streamp) c_int; +pub extern fn deflateReset(stream: z_streamp) ReturnCode; // deflateBound() returns an upper bound on the compressed size after // deflation of sourceLen bytes. It must be called after deflateInit() or @@ -940,280 +940,3 @@ pub const ZlibCompressorArrayList = struct { } } }; - -const CHUNK = 1024 * 64; - -pub const ZlibCompressorStreaming = struct { - mode: NodeMode, - state: z_stream = std.mem.zeroes(z_stream), - chunkSize: c_uint, - flush: FlushValue = .NoFlush, - finishFlush: FlushValue = .Finish, - fullFlush: FlushValue = .FullFlush, - level: c_int, - windowBits: c_int, - memLevel: c_int, - strategy: c_int, - dictionary: []const u8, - err: ReturnCode = .Ok, - err_msg: ?[*:0]const u8 = null, - - pub fn init(this: *ZlibCompressorStreaming) !void { - const ret_code = deflateInit2_(&this.state, this.level, 8, this.windowBits, this.memLevel, this.strategy, zlibVersion(), @sizeOf(z_stream)); - if (ret_code != .Ok) return error.ZlibError9; - - this.setDictionary() catch {}; - this.err_msg = null; - return; - } - - fn setDictionary(this: *ZlibCompressorStreaming) !void { - switch (this.mode) { - .DEFLATE, .DEFLATERAW => { - this.err = deflateSetDictionary(&this.state, this.dictionary.ptr, @intCast(this.dictionary.len)); - }, - .INFLATERAW => { - this.err = inflateSetDictionary(&this.state, this.dictionary.ptr, @intCast(this.dictionary.len)); - }, - else => {}, - } - if (this.err != .Ok) { - return error.ZlibFailedSetDictionary; - } - } - - pub fn params(this: *ZlibCompressorStreaming, level: c_int, strategy: c_int) void { - const err = deflateParams(&this.state, level, strategy); - bun.debugAssert(err == .Ok); - this.level = level; - this.strategy = strategy; - } - - pub fn write(this: *ZlibCompressorStreaming, bytes: []const u8, output: *std.ArrayListUnmanaged(u8), process_all_input: bool) !void { - const state = &this.state; - state.next_in = bytes.ptr; - state.avail_in = @intCast(bytes.len); - if (state.avail_in == 0) state.next_in = null; - - if (!process_all_input) return; - while (true) { - if (try this.doWork(output, this.flush)) { - break; - } - } - // bun.assert(state.avail_in == 0); - } - - pub fn doWork(this: *ZlibCompressorStreaming, output: *std.ArrayListUnmanaged(u8), flush: FlushValue) !bool { - const state = &this.state; - var out: [CHUNK]u8 = undefined; - state.avail_out = CHUNK; - state.next_out = &out; - - this.err = deflate(state, flush); - bun.assert(this.err != .StreamError); - const have = CHUNK - state.avail_out; - try output.appendSlice(bun.default_allocator, out[0..have]); - if (state.avail_out == 0) return false; - return true; - } - - pub fn end(this: *ZlibCompressorStreaming, output: *std.ArrayListUnmanaged(u8)) !void { - const state = &this.state; - state.next_in = null; - state.avail_in = 0; - - const done = try this.doWork(output, this.finishFlush); - bun.assert(done); - // bun.assert(state.avail_in == 0); - - const ret = deflateEnd(&this.state); - bun.assert(ret == .Ok or ret == .StreamEnd); - if (this.err != .StreamEnd and this.finishFlush == .Finish) return error.ZlibError10; - } -}; - -pub const ZlibDecompressorStreaming = struct { - mode: NodeMode, - state: z_stream = std.mem.zeroes(z_stream), - chunkSize: c_uint, - next_expected_header_byte: ?[*]const u8 = null, - gzip_id_bytes_read: u16 = 0, - flush: FlushValue = .NoFlush, - finishFlush: FlushValue = .Finish, - fullFlush: FlushValue = .FullFlush, - windowBits: c_int, - dictionary: []const u8, - err: ReturnCode = .Ok, - err_msg: ?[*:0]const u8 = null, - do_inflate_loop: bool = true, - - pub fn init(this: *ZlibDecompressorStreaming) !void { - const ret_code = inflateInit2_(&this.state, this.windowBits, zlibVersion(), @sizeOf(z_stream)); - if (ret_code != .Ok) return error.ZlibError1; - - this.setDictionary() catch {}; - this.err_msg = null; - return; - } - - fn setDictionary(this: *ZlibDecompressorStreaming) !void { - this.err = .Ok; - const dictionary = this.dictionary; - switch (this.mode) { - .DEFLATE, .DEFLATERAW => { - if (dictionary.len > 0) this.err = deflateSetDictionary(&this.state, dictionary.ptr, @intCast(dictionary.len)); - }, - .INFLATERAW => { - if (dictionary.len > 0) this.err = inflateSetDictionary(&this.state, dictionary.ptr, @intCast(dictionary.len)); - }, - else => {}, - } - if (this.err != .Ok) { - return this.error_for_message("Failed to set dictionary"); - } - } - - fn error_for_message(this: *ZlibDecompressorStreaming, default: [*:0]const u8) error{ZlibError} { - var message = default; - if (this.state.err_msg) |msg| message = msg; - this.err_msg = message; - return error.ZlibError; - } - - pub fn writeAll(this: *ZlibDecompressorStreaming, bytes: []const u8, output: *std.ArrayListUnmanaged(u8), process_all_input: bool) !void { - var index: usize = 0; - while (index != bytes.len) { - index += try this.write(bytes[index..], output, process_all_input); - } - } - - fn write(this: *ZlibDecompressorStreaming, bytes: []const u8, output: *std.ArrayListUnmanaged(u8), process_all_input: bool) !usize { - const state = &this.state; - state.next_in = bytes.ptr; - state.avail_in = @intCast(bytes.len); - if (state.avail_in == 0) state.next_in = null; - - // UNZIP mode allows the input to be either gzip or deflate data and we do a two-byte header detection in order to disambiguate. - // the ordering of this logic is a bit abstract because we dont know ahead of time how large 'bytes' will be. - // additionally, if the first byte is "correct" but the second is not, we don't want to lose it from it being consumed. - // Ref: https://github.com/nodejs/node/blob/v22.8.0/src/node_zlib.cc#L777 - if (this.mode == .UNZIP) { - var redd: usize = 0; - this.do_inflate_loop = false; - - if (bytes.len > 0) { - this.next_expected_header_byte = state.next_in; - } - if (this.gzip_id_bytes_read == 0) { - if (this.next_expected_header_byte == null) { - return 0; - } - - if (this.next_expected_header_byte.?[redd] == GZIP_HEADER_ID1) { - this.gzip_id_bytes_read += 1; - redd += 1; - // next_expected_header_byte++; - - if (bytes.len == 1) { - // The only available byte was already read. - return 1; - } - } else { - // the stream did not match the gzip header, bail. - this.mode = .INFLATE; - return 0; - } - } - if (this.gzip_id_bytes_read == 1) { - if (this.next_expected_header_byte == null) { - return 0; - } - - if (this.next_expected_header_byte.?[redd] == GZIP_HEADER_ID2) { - this.gzip_id_bytes_read += 1; - redd += 1; - // next_expected_header_byte++; - - // the gzip header was found. send the header to inflate() and tell writeAll how much we read to do this detection - // if we continued to doWork right now GZIP_HEADER_ID2 might get processed twice. - this.mode = .GUNZIP; - { - const header = &[_]u8{ GZIP_HEADER_ID1, GZIP_HEADER_ID2 }; - state.next_in = header.ptr; - state.avail_in = @intCast(header.len); - var out: [1]u8 = .{0}; - state.avail_out = 0; - state.next_out = &out; // passing a null pointer here causes it to return Z_STREAM_ERROR so we send zero-length instead. - const ret = inflate(state, this.flush); - bun.assert(ret == .Ok); - } - } else { - // There is no actual difference between INFLATE and INFLATERAW (after initialization). - // the stream only partially matched the gzip header, bail. - this.mode = .INFLATE; - } - return redd; - } - - bun.assert(false); // invalid number of gzip magic number bytes read - } - - // we're passed the header or there was no header. it is now safe to send everying to inflate(). - this.do_inflate_loop = true; - if (!process_all_input) return bytes.len; - while (true) { - if (try this.doWork(output, this.flush)) { - break; - } - } - // bun.assert(state.avail_in == 0); - - return bytes.len; - } - - pub fn doWork(this: *ZlibDecompressorStreaming, output: *std.ArrayListUnmanaged(u8), flush: FlushValue) !bool { - const state = &this.state; - var out: [CHUNK]u8 = undefined; - const len = @min(CHUNK, this.chunkSize); - state.avail_out = len; - state.next_out = &out; - - this.err = inflate(state, flush); - const ret = this.err; - bun.assert(ret != .StreamError); - if (ret == .NeedDict) return this.error_for_message((if (this.dictionary.len == 0) "Missing dictionary" else "Bad dictionary").ptr); - if (ret == .DataError) return this.error_for_message("Zlib error"); - if (ret == .MemError) return error.ZlibError4; - const have = len - state.avail_out; - try output.appendSlice(bun.default_allocator, out[0..have]); - if (ret == .StreamEnd and this.mode == .GUNZIP and state.avail_in > 0 and state.next_in.?[0] != 0) { - _ = inflateReset(state); - return false; - } - if (ret == .StreamEnd) return true; - if (state.avail_out == 0) return false; - if (ret == .BufError and flush == .Finish) return this.error_for_message("unexpected end of file"); - return true; - } - - pub fn end(this: *ZlibDecompressorStreaming, output: *std.ArrayListUnmanaged(u8)) !void { - const state = &this.state; - state.next_in = null; - state.avail_in = 0; - - const done = try this.doWork(output, this.finishFlush); - bun.assert(done); - // bun.assert(state.avail_in == 0); - - const ret = inflateEnd(&this.state); - bun.assert(ret == .Ok or ret == .StreamEnd); - if (this.err != .StreamEnd and this.finishFlush == .Finish) return error.ZlibError8; - } -}; - -// -// - -const GZIP_HEADER_ID1: u8 = 0x1f; -const GZIP_HEADER_ID2: u8 = 0x8b; diff --git a/test/harness.ts b/test/harness.ts index d28882adfb..8abf61302e 100644 --- a/test/harness.ts +++ b/test/harness.ts @@ -425,6 +425,43 @@ expect.extend({ }; } + return { + pass: true, + }; + } + }, + async toThrowWithCodeAsync(fn: CallableFunction, cls: CallableFunction, code: string) { + try { + await fn(); + return { + pass: false, + message: () => `Received function did not throw`, + }; + } catch (e) { + // expect(e).toBeInstanceOf(cls); + if (!(e instanceof cls)) { + return { + pass: false, + message: () => `Expected error to be instanceof ${cls.name}; got ${e.__proto__.constructor.name}`, + }; + } + + // expect(e).toHaveProperty("code"); + if (!("code" in e)) { + return { + pass: false, + message: () => `Expected error to have property 'code'; got ${e}`, + }; + } + + // expect(e.code).toEqual(code); + if (e.code !== code) { + return { + pass: false, + message: () => `Expected error to have code '${code}'; got ${e.code}`, + }; + } + return { pass: true, }; @@ -1078,6 +1115,7 @@ interface BunHarnessTestMatchers { toBeBinaryType(expected: keyof typeof binaryTypes): void; toRun(optionalStdout?: string, expectedCode?: number): void; toThrowWithCode(cls: CallableFunction, code: string): void; + toThrowWithCodeAsync(cls: CallableFunction, code: string): void; } declare module "bun:test" { diff --git a/test/js/node/http/fixtures/http.compress.leak.server.ts b/test/js/node/http/fixtures/http.compress.leak.server.ts new file mode 100644 index 0000000000..102b332d76 --- /dev/null +++ b/test/js/node/http/fixtures/http.compress.leak.server.ts @@ -0,0 +1,58 @@ +import jsc from "bun:jsc"; +import { createServer, Server } from "node:http"; +import { URL } from "node:url"; +import zlib from "node:zlib"; + +const data = `Create Next App
Next.js logo
  1. Get started by editing app/page.tsx.
  2. Save and see your changes instantly.
`; + +function listen(server: Server, protocol: string = "http"): Promise { + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => reject("Timed out"), 5000).unref(); + server.listen({ port: 0 }, (err, hostname, port) => { + clearTimeout(timeout); + + if (err) { + reject(err); + } else { + resolve(new URL(`${protocol}://${hostname}:${port}`)); + } + }); + }); +} + +const baseline = process.memoryUsage.rss(); +let count = 0; + +var server = createServer(async (req, res) => { + res.writeHead(200, { "Content-Type": "application/gzip" }); + const gz = zlib.createGzip(); + + gz.pipe(res); + + for (let i = 0; i < 10; i++) gz.write(data); + await Bun.sleep(10); + for (let i = 0; i < 10; i++) gz.write(data); + await Bun.sleep(10); + for (let i = 0; i < 10; i++) gz.write(data); + for (let i = 0; i < 10; i++) gz.write(data); + await Bun.sleep(10); + for (let i = 0; i < 10; i++) gz.write(data); + for (let i = 0; i < 10; i++) gz.write(data); + for (let i = 0; i < 10; i++) gz.write(data); + gz.end(); + + count += 1; + if (count % 1000 === 0) { + Bun.gc(true); + console.log("count", count, process.memoryUsage.rss()); + } + if (count == 10_000) { + Bun.gc(true); + const after = process.memoryUsage.rss(); + console.log("heapStats", jsc.heapStats()); + process.send({ baseline, after }); + } +}); +const url = await listen(server); +console.log("server", "listening on", url.port); +process.send(url.port); diff --git a/test/js/node/http/node-http.compress.leak.test.ts b/test/js/node/http/node-http.compress.leak.test.ts new file mode 100644 index 0000000000..1962c29a14 --- /dev/null +++ b/test/js/node/http/node-http.compress.leak.test.ts @@ -0,0 +1,38 @@ +import { expect, test } from "bun:test"; +import { bunExe } from "harness"; +import path from "node:path"; + +// test does still leak but not as bad +// prettier-ignore +test.todo("http.Server + compression integration", async () => { + const { promise, resolve } = Promise.withResolvers(); + const proc = Bun.spawn([bunExe(), path.resolve(import.meta.dir, "fixtures", "http.compress.leak.server.ts")], { + stdio: ["ignore", "inherit", "inherit"], + async ipc(message) { + if (typeof message === "string") { + const port = message; + + for (let i = 0; i < 1_000; i++) { + const a = await Promise.all( + Array(10) + .fill(0) + .map(v => fetch(`http://localhost:${port}`, { headers: { "Accept-Encoding": "gzip" } })), + ); + const b = await Promise.all(a.map(v => v.arrayBuffer())); + } + return; + } + if (typeof message === "object") { + const { baseline, after } = message; + console.log(baseline); + console.log(after); + console.log("-", after - baseline); + console.log("-", 1024 * 1024 * 20); + expect(after - baseline).toBeLessThan(1024 * 1024 * 20); + process.kill(proc.pid); // cleanup + resolve(); + } + }, + }); + await promise; +}, 0); diff --git a/test/js/node/test/common/index.js b/test/js/node/test/common/index.js index ea21d15c10..6494f73197 100644 --- a/test/js/node/test/common/index.js +++ b/test/js/node/test/common/index.js @@ -338,9 +338,10 @@ if (global.structuredClone) { knownGlobals.push(global.structuredClone); } -if (global.EventSource) { - knownGlobals.push(EventSource); -} +// BUN:TODO: uncommenting this crashes bun +// if (global.EventSource) { +// knownGlobals.push(EventSource); +// } if (global.fetch) { knownGlobals.push(fetch); diff --git a/test/js/node/test/parallel/zlib-brotli-16gb.test.js b/test/js/node/test/parallel/zlib-brotli-16gb.test.js new file mode 100644 index 0000000000..edefbaafe9 --- /dev/null +++ b/test/js/node/test/parallel/zlib-brotli-16gb.test.js @@ -0,0 +1,59 @@ +//#FILE: test-zlib-brotli-16GB.js +//#SHA1: 0c5e79f3713fdd0597dda7e531484a0ef3170578 +//----------------- +"use strict"; + +const { createBrotliDecompress } = require("node:zlib"); +const { getDefaultHighWaterMark } = require("stream"); + +// This tiny HEX string is a 16GB file. +// This test verifies that the stream actually stops. +const content = + "cfffff7ff82700e2b14020f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c32200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdf" + + "fe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8de" + + "fff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074e" + + "ffff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500ba" + + "f7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200d" + + "dfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180" + + "eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b0004" + + "0f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800" + + "a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c0" + + "0d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c16" + + "00e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0" + + "b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f011087" + + "0500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c" + + "30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4" + + "610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e" + + "2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300" + + "715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe098" + + "0382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04" + + "401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f0" + + "2200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f" + + "0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19" + + "f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff0" + + "4f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff" + + "82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3f" + + "fc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1" + + "ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bffcfffff7ff82700e2b00040f7fe9ffffffff04f00c4610180eefd3fffffffe19f0088c30200ddfb7ffeffffc33f0110870500baf7fffcffff877f02200e0b0074effff9ffff0fff04401c1600e8defff3ffff1ffe0980382c00d0bdffe7ffff3ffc1300715800a07bff3f"; + +test("the test", async () => { + const buf = Buffer.from(content, "hex"); + + const decoder = createBrotliDecompress(); + decoder.end(buf); + + const { promise, resolve, reject } = Promise.withResolvers(); + + setTimeout(() => { + try { + expect(decoder._readableState.buffer.length).toBe(getDefaultHighWaterMark() / (16 * 1024)); + resolve(); + } catch (e) { + reject(e); + } + }, 500); + await promise; +}); + +//#FILE: test-zlib-brotli-16GB.js +//----------------- diff --git a/test/js/node/test/parallel/zlib-brotli-flush.test.js b/test/js/node/test/parallel/zlib-brotli-flush.test.js new file mode 100644 index 0000000000..77723ad78f --- /dev/null +++ b/test/js/node/test/parallel/zlib-brotli-flush.test.js @@ -0,0 +1,33 @@ +//#FILE: test-zlib-brotli-flush.js +//#SHA1: b0a953be98db6dd674668bfd6cffa3e283144ad1 +//----------------- +"use strict"; +const zlib = require("zlib"); +const fs = require("fs"); +const path = require("path"); + +const fixturesPath = path.join(__dirname, "..", "fixtures"); +const file = fs.readFileSync(path.join(fixturesPath, "person.jpg")); +const chunkSize = 16; + +test("BrotliCompress flush should produce expected output", done => { + const deflater = new zlib.BrotliCompress(); + const chunk = file.slice(0, chunkSize); + const expectedFull = Buffer.from("iweA/9j/4AAQSkZJRgABAQEASA==", "base64"); + let actualFull; + + deflater.write(chunk, () => { + deflater.flush(() => { + const bufs = []; + let buf; + while ((buf = deflater.read()) !== null) { + bufs.push(buf); + } + actualFull = Buffer.concat(bufs); + expect(actualFull).toEqual(expectedFull); + done(); + }); + }); +}); + +//<#END_FILE: test-zlib-brotli-flush.js diff --git a/test/js/node/test/parallel/zlib-brotli-kmaxlength-rangeerror.test.js b/test/js/node/test/parallel/zlib-brotli-kmaxlength-rangeerror.test.js new file mode 100644 index 0000000000..c3cbb9d19b --- /dev/null +++ b/test/js/node/test/parallel/zlib-brotli-kmaxlength-rangeerror.test.js @@ -0,0 +1,29 @@ +//#FILE: test-zlib-brotli-kmaxlength-rangeerror.js +//#SHA1: f0d3ad25e8a844b31b7e14ab68a84182fd5f52b7 +//----------------- +"use strict"; + +const util = require("util"); + +// Change kMaxLength for zlib to trigger the error without having to allocate large Buffers. +const buffer = require("buffer"); +const oldkMaxLength = buffer.kMaxLength; +buffer.kMaxLength = 64; +const zlib = require("zlib"); +buffer.kMaxLength = oldkMaxLength; + +// Create a large input buffer +const encoded = Buffer.from("G38A+CXCIrFAIAM=", "base64"); + +test("brotliDecompress throws RangeError for large output (async)", async () => { + await expect(async () => util.promisify(zlib.brotliDecompress)(encoded)).toThrowWithCodeAsync( + RangeError, + "ERR_BUFFER_TOO_LARGE", + ); +}, 1000); + +test("brotliDecompressSync throws RangeError for large output", () => { + expect(() => zlib.brotliDecompressSync(encoded)).toThrowWithCode(RangeError, "ERR_BUFFER_TOO_LARGE"); +}); + +//<#END_FILE: test-zlib-brotli-kmaxlength-rangeerror.js diff --git a/test/js/node/test/parallel/zlib-brotli.test.js b/test/js/node/test/parallel/zlib-brotli.test.js index e97933cab8..d61df3eae3 100644 --- a/test/js/node/test/parallel/zlib-brotli.test.js +++ b/test/js/node/test/parallel/zlib-brotli.test.js @@ -30,35 +30,35 @@ test("Quality parameter at stream creation", () => { }); test("Setting out-of-bounds option values or keys", () => { - // expect(() => { - // zlib.createBrotliCompress({ - // params: { - // 10000: 0, - // }, - // }); - // }).toThrow( - // expect.objectContaining({ - // code: "ERR_BROTLI_INVALID_PARAM", - // name: "RangeError", - // message: expect.any(String), - // }), - // ); + expect(() => { + zlib.createBrotliCompress({ + params: { + 10000: 0, + }, + }); + }).toThrow( + expect.objectContaining({ + code: "ERR_BROTLI_INVALID_PARAM", + name: "RangeError", + message: expect.any(String), + }), + ); // Test that accidentally using duplicate keys fails. - // expect(() => { - // zlib.createBrotliCompress({ - // params: { - // 0: 0, - // "00": 0, - // }, - // }); - // }).toThrow( - // expect.objectContaining({ - // code: "ERR_BROTLI_INVALID_PARAM", - // name: "RangeError", - // message: expect.any(String), - // }), - // ); + expect(() => { + zlib.createBrotliCompress({ + params: { + 0: 0, + "00": 0, + }, + }); + }).toThrow( + expect.objectContaining({ + code: "ERR_BROTLI_INVALID_PARAM", + name: "RangeError", + message: expect.any(String), + }), + ); expect(() => { zlib.createBrotliCompress({ diff --git a/test/js/node/test/parallel/zlib-close-in-ondata.test.js b/test/js/node/test/parallel/zlib-close-in-ondata.test.js new file mode 100644 index 0000000000..e4449d57d4 --- /dev/null +++ b/test/js/node/test/parallel/zlib-close-in-ondata.test.js @@ -0,0 +1,23 @@ +//#FILE: test-zlib-close-in-ondata.js +//#SHA1: 8218c0461dd0733882aaf37688e3b32b164e3535 +//----------------- +"use strict"; + +const zlib = require("zlib"); + +test("zlib stream closes in ondata event", done => { + const ts = zlib.createGzip(); + const buf = Buffer.alloc(1024 * 1024 * 20); + + ts.on( + "data", + jest.fn(() => { + ts.close(); + done(); + }), + ); + + ts.end(buf); +}); + +//<#END_FILE: test-zlib-close-in-ondata.js diff --git a/test/js/node/test/parallel/zlib-convenience-methods.test.js b/test/js/node/test/parallel/zlib-convenience-methods.test.js new file mode 100644 index 0000000000..f1c853be23 --- /dev/null +++ b/test/js/node/test/parallel/zlib-convenience-methods.test.js @@ -0,0 +1,96 @@ +//#FILE: test-zlib-convenience-methods.js +//#SHA1: e215a52650eaa95999dbb7d77f5b03376cdc673b +//----------------- +"use strict"; + +const zlib = require("zlib"); +const util = require("util"); +const { getBufferSources } = require("../common"); + +// Must be a multiple of 4 characters in total to test all ArrayBufferView +// types. +const expectStr = "blah".repeat(8); +const expectBuf = Buffer.from(expectStr); + +const opts = { + level: 9, + chunkSize: 1024, +}; + +const optsInfo = { + info: true, +}; + +const methodPairs = [ + ["gzip", "gunzip", "Gzip", "Gunzip"], + ["gzip", "unzip", "Gzip", "Unzip"], + ["deflate", "inflate", "Deflate", "Inflate"], + ["deflateRaw", "inflateRaw", "DeflateRaw", "InflateRaw"], + ["brotliCompress", "brotliDecompress", "BrotliCompress", "BrotliDecompress"], +]; + +const testCases = [ + ["string", expectStr], + ["Buffer", expectBuf], + ...getBufferSources(expectBuf).map(obj => [obj[Symbol.toStringTag], obj]), +]; + +describe("zlib convenience methods", () => { + describe.each(testCases)("%s input", (type, expected) => { + for (const [compress, decompress, CompressClass, DecompressClass] of methodPairs) { + describe(`${compress}/${decompress}`, async () => { + test("Async with options", async () => { + const c = await util.promisify(zlib[compress])(expected, opts); + const d = await util.promisify(zlib[decompress])(c, opts); + expect(d.toString()).toEqual(expectStr); + }); + + test("Async without options", async () => { + const c = await util.promisify(zlib[compress])(expected); + const d = await util.promisify(zlib[decompress])(c); + expect(d.toString()).toEqual(expectStr); + }); + + test("Async with info option", async () => { + const c = await util.promisify(zlib[compress])(expected, optsInfo); + expect(c.engine).toBeInstanceOf(zlib[CompressClass]); + const d = await util.promisify(zlib[decompress])(c.buffer, optsInfo); + expect(d.engine).toBeInstanceOf(zlib[DecompressClass]); + expect(d.buffer.toString()).toEqual(expectStr); + }); + + test("Sync with options", () => { + const c = zlib[compress + "Sync"](expected, opts); + const d = zlib[decompress + "Sync"](c, opts); + expect(d.toString()).toEqual(expectStr); + }); + + test("Sync without options", async () => { + const c = zlib[compress + "Sync"](expected); + const d = zlib[decompress + "Sync"](c); + expect(d.toString()).toEqual(expectStr); + }); + + test("Sync with info option", () => { + const c = zlib[compress + "Sync"](expected, optsInfo); + expect(c.engine).toBeInstanceOf(zlib[CompressClass]); + const d = zlib[decompress + "Sync"](c.buffer, optsInfo); + expect(d.engine).toBeInstanceOf(zlib[DecompressClass]); + expect(d.buffer.toString()).toEqual(expectStr); + }); + }); + } + }); + + test("throws error when callback is not provided", () => { + expect(() => zlib.gzip("abc")).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + message: expect.stringContaining('The "callback" argument must be of type function'), + }), + ); + }); +}); + +//<#END_FILE: test-zlib-convenience-methods.js diff --git a/test/js/node/test/parallel/zlib-destroy-pipe.test.js b/test/js/node/test/parallel/zlib-destroy-pipe.test.js new file mode 100644 index 0000000000..90866882de --- /dev/null +++ b/test/js/node/test/parallel/zlib-destroy-pipe.test.js @@ -0,0 +1,24 @@ +//#FILE: test-zlib-destroy-pipe.js +//#SHA1: 55e5ddd18c87bc58f331f82caa482cd49f5de168 +//----------------- +"use strict"; + +const zlib = require("zlib"); +const { Writable } = require("stream"); + +test("verify that the zlib transform does not error in case it is destroyed with data still in flight", () => { + const ts = zlib.createGzip(); + + const ws = new Writable({ + write(chunk, enc, cb) { + setImmediate(cb); + ts.destroy(); + }, + }); + + const buf = Buffer.allocUnsafe(1024 * 1024 * 20); + ts.end(buf); + ts.pipe(ws); +}); + +//<#END_FILE: test-zlib-destroy-pipe.js diff --git a/test/js/node/test/parallel/zlib-destroy.test.js b/test/js/node/test/parallel/zlib-destroy.test.js new file mode 100644 index 0000000000..0947ce2827 --- /dev/null +++ b/test/js/node/test/parallel/zlib-destroy.test.js @@ -0,0 +1,40 @@ +//#FILE: test-zlib-destroy.js +//#SHA1: b28cfad7c9e73659c624238b74dcc38146c94203 +//----------------- +"use strict"; + +const zlib = require("zlib"); + +// Verify that the zlib transform does clean up +// the handle when calling destroy. + +test("zlib transform cleans up handle on destroy", done => { + const ts = zlib.createGzip(); + ts.destroy(); + expect(ts._handle).toBeNull(); + + ts.on("close", () => { + ts.close(() => { + done(); + }); + }); +}); + +test("error is only emitted once", done => { + const decompress = zlib.createGunzip(15); + + let errorCount = 0; + decompress.on("error", err => { + errorCount++; + decompress.close(); + + // Ensure this callback is only called once + expect(errorCount).toBe(1); + done(); + }); + + decompress.write("something invalid"); + decompress.destroy(new Error("asd")); +}); + +//<#END_FILE: test-zlib-destroy.js diff --git a/test/js/node/test/parallel/zlib-dictionary.test.js b/test/js/node/test/parallel/zlib-dictionary.test.js new file mode 100644 index 0000000000..e7b9fbc82a --- /dev/null +++ b/test/js/node/test/parallel/zlib-dictionary.test.js @@ -0,0 +1,176 @@ +//#FILE: test-zlib-dictionary.js +//#SHA1: b5fc5a33125dfeaa9965b0564a8196b056b95918 +//----------------- +"use strict"; + +const zlib = require("zlib"); + +const spdyDict = Buffer.from( + [ + "optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-", + "languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi", + "f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser", + "-agent10010120020120220320420520630030130230330430530630740040140240340440", + "5406407408409410411412413414415416417500501502503504505accept-rangesageeta", + "glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic", + "ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran", + "sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati", + "oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo", + "ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe", + "pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic", + "ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1", + ".1statusversionurl\0", + ].join(""), +); + +const input = ["HTTP/1.1 200 Ok", "Server: node.js", "Content-Length: 0", ""].join("\r\n"); + +function basicDictionaryTest(spdyDict) { + return new Promise(resolve => { + let output = ""; + const deflate = zlib.createDeflate({ dictionary: spdyDict }); + const inflate = zlib.createInflate({ dictionary: spdyDict }); + inflate.setEncoding("utf-8"); + + deflate.on("data", chunk => { + inflate.write(chunk); + }); + + inflate.on("data", chunk => { + output += chunk; + }); + + deflate.on("end", () => { + inflate.end(); + }); + + inflate.on("end", () => { + expect(output).toBe(input); + resolve(); + }); + + deflate.write(input); + deflate.end(); + }); +} + +function deflateResetDictionaryTest(spdyDict) { + return new Promise(resolve => { + let doneReset = false; + let output = ""; + const deflate = zlib.createDeflate({ dictionary: spdyDict }); + const inflate = zlib.createInflate({ dictionary: spdyDict }); + inflate.setEncoding("utf-8"); + + deflate.on("data", chunk => { + if (doneReset) inflate.write(chunk); + }); + + inflate.on("data", chunk => { + output += chunk; + }); + + deflate.on("end", () => { + inflate.end(); + }); + + inflate.on("end", () => { + expect(output).toBe(input); + resolve(); + }); + + deflate.write(input); + deflate.flush(() => { + deflate.reset(); + doneReset = true; + deflate.write(input); + deflate.end(); + }); + }); +} + +function rawDictionaryTest(spdyDict) { + return new Promise(resolve => { + let output = ""; + const deflate = zlib.createDeflateRaw({ dictionary: spdyDict }); + const inflate = zlib.createInflateRaw({ dictionary: spdyDict }); + inflate.setEncoding("utf-8"); + + deflate.on("data", chunk => { + inflate.write(chunk); + }); + + inflate.on("data", chunk => { + output += chunk; + }); + + deflate.on("end", () => { + inflate.end(); + }); + + inflate.on("end", () => { + expect(output).toBe(input); + resolve(); + }); + + deflate.write(input); + deflate.end(); + }); +} + +function deflateRawResetDictionaryTest(spdyDict) { + return new Promise(resolve => { + let doneReset = false; + let output = ""; + const deflate = zlib.createDeflateRaw({ dictionary: spdyDict }); + const inflate = zlib.createInflateRaw({ dictionary: spdyDict }); + inflate.setEncoding("utf-8"); + + deflate.on("data", chunk => { + if (doneReset) inflate.write(chunk); + }); + + inflate.on("data", chunk => { + output += chunk; + }); + + deflate.on("end", () => { + inflate.end(); + }); + + inflate.on("end", () => { + expect(output).toBe(input); + resolve(); + }); + + deflate.write(input); + deflate.flush(() => { + deflate.reset(); + doneReset = true; + deflate.write(input); + deflate.end(); + }); + }); +} + +const dictionaries = [spdyDict, Buffer.from(spdyDict), new Uint8Array(spdyDict)]; + +describe("zlib dictionary tests", () => { + test.each(dictionaries)("basic dictionary test", async dict => { + await basicDictionaryTest(dict); + }); + + test.each(dictionaries)("deflate reset dictionary test", async dict => { + await deflateResetDictionaryTest(dict); + }); + + test.each(dictionaries)("raw dictionary test", async dict => { + await rawDictionaryTest(dict); + }); + + test.each(dictionaries)("deflate raw reset dictionary test", async dict => { + await deflateRawResetDictionaryTest(dict); + }); +}); + +//<#END_FILE: test-zlib-dictionary.js diff --git a/test/js/node/test/parallel/zlib-flush-drain-longblock.test.js b/test/js/node/test/parallel/zlib-flush-drain-longblock.test.js new file mode 100644 index 0000000000..21f8a7d720 --- /dev/null +++ b/test/js/node/test/parallel/zlib-flush-drain-longblock.test.js @@ -0,0 +1,34 @@ +//#FILE: test-zlib-flush-drain-longblock.js +//#SHA1: 95927f13fbb59e0a8a2a32c14d0443fc110bab6e +//----------------- +"use strict"; + +const zlib = require("zlib"); + +test("zlib flush interacts properly with writableState.needDrain", done => { + const zipper = zlib.createGzip({ highWaterMark: 16384 }); + const unzipper = zlib.createGunzip(); + zipper.pipe(unzipper); + + zipper.write("A".repeat(17000)); + zipper.flush(); + + let received = 0; + let dataCallCount = 0; + + unzipper.on("data", d => { + received += d.length; + dataCallCount++; + }); + + unzipper.on("end", () => { + expect(received).toBe(17000); + expect(dataCallCount).toBeGreaterThanOrEqual(2); + done(); + }); + + // Properly end the streams to ensure all data is processed + zipper.end(); +}); + +//<#END_FILE: test-zlib-flush-drain-longblock.js diff --git a/test/js/node/test/parallel/zlib-flush-drain.test.js b/test/js/node/test/parallel/zlib-flush-drain.test.js new file mode 100644 index 0000000000..b4407e7cbd --- /dev/null +++ b/test/js/node/test/parallel/zlib-flush-drain.test.js @@ -0,0 +1,53 @@ +//#FILE: test-zlib-flush-drain.js +//#SHA1: 2f83bee63a56543c9824833e4fa7d8f5b33a373e +//----------------- +"use strict"; +const zlib = require("zlib"); + +const bigData = Buffer.alloc(10240, "x"); + +const opts = { + level: 0, + highWaterMark: 16, +}; + +let flushCount = 0; +let drainCount = 0; +let beforeFlush, afterFlush; + +test("zlib flush and drain behavior", done => { + const deflater = zlib.createDeflate(opts); + + // Shim deflater.flush so we can count times executed + const flush = deflater.flush; + deflater.flush = function (kind, callback) { + flushCount++; + flush.call(this, kind, callback); + }; + + deflater.write(bigData); + + const ws = deflater._writableState; + beforeFlush = ws.needDrain; + + deflater.on("data", () => {}); + + deflater.flush(function (err) { + afterFlush = ws.needDrain; + }); + + deflater.on("drain", function () { + drainCount++; + }); + + // Use setTimeout to ensure all asynchronous operations have completed + setTimeout(() => { + expect(beforeFlush).toBe(true); + expect(afterFlush).toBe(false); + expect(drainCount).toBe(1); + expect(flushCount).toBe(1); + done(); + }, 100); +}); + +//<#END_FILE: test-zlib-flush-drain.js diff --git a/test/js/node/test/parallel/zlib-flush-write-sync-interleaved.test.js b/test/js/node/test/parallel/zlib-flush-write-sync-interleaved.test.js new file mode 100644 index 0000000000..1661372b3d --- /dev/null +++ b/test/js/node/test/parallel/zlib-flush-write-sync-interleaved.test.js @@ -0,0 +1,66 @@ +//#FILE: test-zlib-flush-write-sync-interleaved.js +//#SHA1: 35bfe36486f112686943448a115a586035455ba7 +//----------------- +"use strict"; +const { createGzip, createGunzip, Z_PARTIAL_FLUSH } = require("zlib"); + +// Verify that .flush() behaves like .write() in terms of ordering, e.g. in +// a sequence like .write() + .flush() + .write() + .flush() each .flush() call +// only affects the data written before it. +// Refs: https://github.com/nodejs/node/issues/28478 + +test("zlib flush and write ordering", done => { + const compress = createGzip(); + const decompress = createGunzip(); + decompress.setEncoding("utf8"); + + const events = []; + const compressedChunks = []; + + for (const chunk of ["abc", "def", "ghi"]) { + compress.write(chunk, () => events.push({ written: chunk })); + compress.flush(Z_PARTIAL_FLUSH, () => { + events.push("flushed"); + const chunk = compress.read(); + if (chunk !== null) compressedChunks.push(chunk); + }); + } + + compress.end(() => { + events.push("compress end"); + writeToDecompress(); + }); + + function writeToDecompress() { + // Write the compressed chunks to a decompressor, one by one, in order to + // verify that the flushes actually worked. + const chunk = compressedChunks.shift(); + if (chunk === undefined) { + decompress.end(); + checkResults(); + return; + } + decompress.write(chunk, () => { + events.push({ read: decompress.read() }); + writeToDecompress(); + }); + } + + function checkResults() { + expect(events).toEqual([ + { written: "abc" }, + "flushed", + { written: "def" }, + "flushed", + { written: "ghi" }, + "flushed", + "compress end", + { read: "abc" }, + { read: "def" }, + { read: "ghi" }, + ]); + done(); + } +}); + +//<#END_FILE: test-zlib-flush-write-sync-interleaved.js diff --git a/test/js/node/test/parallel/zlib-flush.test.js b/test/js/node/test/parallel/zlib-flush.test.js new file mode 100644 index 0000000000..cfda6ffb6d --- /dev/null +++ b/test/js/node/test/parallel/zlib-flush.test.js @@ -0,0 +1,38 @@ +//#FILE: test-zlib-flush.js +//#SHA1: 61f325893c63c826c2a498bc52ef3401f9a5a542 +//----------------- +"use strict"; + +const zlib = require("node:zlib"); + +test("zlib flush", async () => { + const opts = { level: 0 }; + const deflater = zlib.createDeflate(opts); + const chunk = Buffer.from("/9j/4AAQSkZJRgABAQEASA==", "base64"); + const expectedNone = Buffer.from([0x78, 0x01]); + const blkhdr = Buffer.from([0x00, 0x10, 0x00, 0xef, 0xff]); + const adler32 = Buffer.from([0x00, 0x00, 0x00, 0xff, 0xff]); + const expectedFull = Buffer.concat([blkhdr, chunk, adler32]); + let actualNone; + let actualFull; + + await new Promise(resolve => { + deflater.write(chunk, function () { + deflater.flush(zlib.constants.Z_NO_FLUSH, function () { + actualNone = deflater.read(); + deflater.flush(function () { + const bufs = []; + let buf; + while ((buf = deflater.read()) !== null) bufs.push(buf); + actualFull = Buffer.concat(bufs); + resolve(); + }); + }); + }); + }); + + expect(actualNone).toEqual(expectedNone); + expect(actualFull).toEqual(expectedFull); +}); + +//<#END_FILE: test-zlib-flush.js diff --git a/test/js/node/test/parallel/zlib-from-gzip-with-trailing-garbage.test.js b/test/js/node/test/parallel/zlib-from-gzip-with-trailing-garbage.test.js new file mode 100644 index 0000000000..4b2371eabd --- /dev/null +++ b/test/js/node/test/parallel/zlib-from-gzip-with-trailing-garbage.test.js @@ -0,0 +1,76 @@ +//#FILE: test-zlib-from-gzip-with-trailing-garbage.js +//#SHA1: 8c3ebbede1912a48995aaada3c3e470d48bc01f3 +//----------------- +"use strict"; +const zlib = require("zlib"); + +describe("Unzipping a gzip file with trailing garbage", () => { + test("Should ignore trailing null-bytes", () => { + const data = Buffer.concat([zlib.gzipSync("abc"), zlib.gzipSync("def"), Buffer.alloc(10)]); + + expect(zlib.gunzipSync(data).toString()).toBe("abcdef"); + }); + + test("Should ignore trailing null-bytes (async)", async () => { + const data = Buffer.concat([zlib.gzipSync("abc"), zlib.gzipSync("def"), Buffer.alloc(10)]); + + const result = await new Promise((resolve, reject) => { + zlib.gunzip(data, (err, result) => { + if (err) reject(err); + else resolve(result); + }); + }); + + expect(result.toString()).toBe("abcdef"); + }); + + test("Should throw error if trailing garbage looks like a gzip header", () => { + const data = Buffer.concat([ + zlib.gzipSync("abc"), + zlib.gzipSync("def"), + Buffer.from([0x1f, 0x8b, 0xff, 0xff]), + Buffer.alloc(10), + ]); + + expect(() => zlib.gunzipSync(data)).toThrow("unknown compression method"); + }); + + test("Should throw error if trailing garbage looks like a gzip header (async)", async () => { + const data = Buffer.concat([ + zlib.gzipSync("abc"), + zlib.gzipSync("def"), + Buffer.from([0x1f, 0x8b, 0xff, 0xff]), + Buffer.alloc(10), + ]); + + await expect( + new Promise((resolve, reject) => { + zlib.gunzip(data, (err, result) => { + if (err) reject(err); + else resolve(result); + }); + }), + ).rejects.toThrow("unknown compression method"); + }); + + test("Should throw error if trailing junk is too short to be a gzip segment", () => { + const data = Buffer.concat([zlib.gzipSync("abc"), zlib.gzipSync("def"), Buffer.from([0x1f, 0x8b, 0xff, 0xff])]); + + expect(() => zlib.gunzipSync(data)).toThrow("unknown compression method"); + }); + + test("Should throw error if trailing junk is too short to be a gzip segment (async)", async () => { + const data = Buffer.concat([zlib.gzipSync("abc"), zlib.gzipSync("def"), Buffer.from([0x1f, 0x8b, 0xff, 0xff])]); + + await expect( + new Promise((resolve, reject) => { + zlib.gunzip(data, (err, result) => { + if (err) reject(err); + else resolve(result); + }); + }), + ).rejects.toThrow("unknown compression method"); + }); +}); + +//<#END_FILE: test-zlib-from-gzip-with-trailing-garbage.js diff --git a/test/js/node/test/parallel/zlib-from-gzip.test.js b/test/js/node/test/parallel/zlib-from-gzip.test.js new file mode 100644 index 0000000000..c1406008f2 --- /dev/null +++ b/test/js/node/test/parallel/zlib-from-gzip.test.js @@ -0,0 +1,41 @@ +//#FILE: test-zlib-from-gzip.js +//#SHA1: 44570a611316087d64497151e9ed570aca9060d5 +//----------------- +"use strict"; + +import { tmpdirSync } from "harness"; + +const assert = require("assert"); +const path = require("path"); +const zlib = require("zlib"); +const fixtures = require("../common/fixtures"); +const fs = require("fs"); + +test("test unzipping a file that was created with a non-node gzip lib, piped in as fast as possible", async () => { + const x = tmpdirSync(); + const gunzip = zlib.createGunzip(); + const fixture = fixtures.path("person.jpg.gz"); + const unzippedFixture = fixtures.path("person.jpg"); + const outputFile = path.resolve(x, "person.jpg"); + const expected = fs.readFileSync(unzippedFixture); + const inp = fs.createReadStream(fixture); + const out = fs.createWriteStream(outputFile); + + inp.pipe(gunzip).pipe(out); + + const { promise, resolve, reject } = Promise.withResolvers(); + + out.on("close", () => { + try { + const actual = fs.readFileSync(outputFile); + expect(actual.length).toBe(expected.length); + expect(actual).toEqual(expected); + resolve(); + } catch (e) { + reject(e); + } + }); + await promise; +}); + +//<#END_FILE: test-zlib-from-gzip.js diff --git a/test/js/node/test/parallel/zlib-invalid-input-memory.test.js b/test/js/node/test/parallel/zlib-invalid-input-memory.test.js new file mode 100644 index 0000000000..7193d0a531 --- /dev/null +++ b/test/js/node/test/parallel/zlib-invalid-input-memory.test.js @@ -0,0 +1,30 @@ +//#FILE: test-zlib-invalid-input-memory.js +//#SHA1: 2607db89f2850bfbe75959ca2cd56e647b9eac78 +//----------------- +"use strict"; +const zlib = require("zlib"); +const onGC = require("../common/ongc"); +const common = require("../common"); + +const ongc = common.mustCall(); + +test.todo("zlib context with error can be garbage collected", () => { + const input = Buffer.from("foobar"); + const strm = zlib.createInflate(); + + strm.end(input); + + strm.once("error", err => { + expect(err).toBeTruthy(); + + setImmediate(() => { + global.gc(); + // Keep the event loop alive for seeing the async_hooks destroy hook we use for GC tracking... + // TODO(addaleax): This should maybe not be necessary? + setImmediate(() => {}); + }); + }); + onGC(strm, { ongc }); +}); + +//<#END_FILE: test-zlib-invalid-input-memory.js diff --git a/test/js/node/test/parallel/zlib-invalid-input.test.js b/test/js/node/test/parallel/zlib-invalid-input.test.js new file mode 100644 index 0000000000..aeb28f609d --- /dev/null +++ b/test/js/node/test/parallel/zlib-invalid-input.test.js @@ -0,0 +1,70 @@ +//#FILE: test-zlib-invalid-input.js +//#SHA1: b76d7dde545ea75c25000ee36864841cf73951e0 +//----------------- +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +"use strict"; +// Test uncompressing invalid input + +const zlib = require("zlib"); + +const nonStringInputs = [1, true, { a: 1 }, ["a"]]; + +// zlib.Unzip classes need to get valid data, or else they'll throw. +const unzips = [zlib.Unzip(), zlib.Gunzip(), zlib.Inflate(), zlib.InflateRaw(), zlib.BrotliDecompress()]; + +test("zlib.gunzip throws TypeError for non-string inputs", () => { + nonStringInputs.forEach(input => { + expect(() => { + zlib.gunzip(input); + }).toThrow( + expect.objectContaining({ + name: "TypeError", + code: "ERR_INVALID_ARG_TYPE", + message: expect.any(String), + }), + ); + }); +}); + +test("unzip classes emit error for invalid compressed data", done => { + let completedCount = 0; + + unzips.forEach((uz, i) => { + uz.on("error", err => { + expect(err).toBeTruthy(); + completedCount++; + if (completedCount === unzips.length) { + done(); + } + }); + + uz.on("end", () => { + throw new Error("end event should not be emitted"); + }); + + // This will trigger error event + uz.write("this is not valid compressed data."); + }); +}); + +//<#END_FILE: test-zlib-invalid-input.js diff --git a/test/js/node/test/parallel/zlib-kmaxlength-rangeerror.test.js b/test/js/node/test/parallel/zlib-kmaxlength-rangeerror.test.js new file mode 100644 index 0000000000..9d61c6ba81 --- /dev/null +++ b/test/js/node/test/parallel/zlib-kmaxlength-rangeerror.test.js @@ -0,0 +1,30 @@ +//#FILE: test-zlib-kmaxlength-rangeerror.js +//#SHA1: b6507dfb859656a2d5194153ddf45de79e0ef0ce +//----------------- +"use strict"; + +const util = require("util"); + +// Change kMaxLength for zlib to trigger the error without having to allocate large Buffers. +const buffer = require("buffer"); +const oldkMaxLength = buffer.kMaxLength; +buffer.kMaxLength = 64; +const zlib = require("zlib"); +buffer.kMaxLength = oldkMaxLength; + +const encoded = Buffer.from("H4sIAAAAAAAAA0tMHFgAAIw2K/GAAAAA", "base64"); + +describe("ensure that zlib throws a RangeError if the final buffer needs to be larger than kMaxLength and concatenation fails", () => { + test("sync", () => { + expect(() => zlib.gunzipSync(encoded)).toThrowWithCode(RangeError, "ERR_BUFFER_TOO_LARGE"); + }); + + test("async", async () => { + await expect(async () => util.promisify(zlib.gunzip)(encoded)).toThrowWithCodeAsync( + RangeError, + "ERR_BUFFER_TOO_LARGE", + ); + }); +}); + +//<#END_FILE: test-zlib-kmaxlength-rangeerror.js diff --git a/test/js/node/test/parallel/zlib-maxoutputlength.test.js b/test/js/node/test/parallel/zlib-maxoutputlength.test.js new file mode 100644 index 0000000000..2223043725 --- /dev/null +++ b/test/js/node/test/parallel/zlib-maxoutputlength.test.js @@ -0,0 +1,45 @@ +//#FILE: test-zlib-maxOutputLength.js +//#SHA1: 807eb08c901d7476140bcb35b519518450bef3e6 +//----------------- +"use strict"; +const zlib = require("zlib"); +const { promisify } = require("util"); + +const brotliDecompressAsync = promisify(zlib.brotliDecompress); + +const encoded = Buffer.from("G38A+CXCIrFAIAM=", "base64"); + +describe("zlib.brotliDecompress with maxOutputLength", () => { + test("Async: should throw ERR_BUFFER_TOO_LARGE when maxOutputLength is exceeded", async () => { + await expect(brotliDecompressAsync(encoded, { maxOutputLength: 64 })).rejects.toThrow( + expect.objectContaining({ + code: "ERR_BUFFER_TOO_LARGE", + message: expect.stringContaining("Cannot create a Buffer larger than 64 bytes"), + }), + ); + }); + + test("Sync: should throw ERR_BUFFER_TOO_LARGE when maxOutputLength is exceeded", () => { + expect(() => { + zlib.brotliDecompressSync(encoded, { maxOutputLength: 64 }); + }).toThrow( + expect.objectContaining({ + name: "RangeError", + code: "ERR_BUFFER_TOO_LARGE", + message: expect.stringContaining("Cannot create a Buffer larger than 64 bytes"), + }), + ); + }); + + test("Async: should not throw error when maxOutputLength is sufficient", async () => { + await brotliDecompressAsync(encoded, { maxOutputLength: 256 }); + }); + + test("Sync: should not throw error when maxOutputLength is sufficient", () => { + expect(() => { + zlib.brotliDecompressSync(encoded, { maxOutputLength: 256 }); + }).not.toThrow(); + }); +}); + +//<#END_FILE: test-zlib-maxOutputLength.js diff --git a/test/js/node/test/parallel/zlib-not-string-or-buffer.test.js b/test/js/node/test/parallel/zlib-not-string-or-buffer.test.js index 5353bea54f..626430ec72 100644 --- a/test/js/node/test/parallel/zlib-not-string-or-buffer.test.js +++ b/test/js/node/test/parallel/zlib-not-string-or-buffer.test.js @@ -16,9 +16,6 @@ test("zlib.deflateSync throws for invalid input types", () => { expect.objectContaining({ code: "ERR_INVALID_ARG_TYPE", name: "TypeError", - message: expect.stringContaining( - 'The "buffer" argument must be of type string or an instance of Buffer, TypedArray, DataView, or ArrayBuffer.', - ), }), ); }); diff --git a/test/js/node/test/parallel/zlib-params.test.js b/test/js/node/test/parallel/zlib-params.test.js new file mode 100644 index 0000000000..2c8063d78a --- /dev/null +++ b/test/js/node/test/parallel/zlib-params.test.js @@ -0,0 +1,49 @@ +//#FILE: test-zlib-params.js +//#SHA1: d7d1b0c78ae9b4b5df5a1057c18fc7f2ef735526 +//----------------- +"use strict"; +const zlib = require("zlib"); +const fs = require("fs"); +const path = require("path"); + +const fixturesPath = path.join(__dirname, "..", "fixtures"); +const file = fs.readFileSync(path.join(fixturesPath, "person.jpg")); +const chunkSize = 12 * 1024; +const opts = { level: 9, strategy: zlib.constants.Z_DEFAULT_STRATEGY }; + +test("zlib params change mid-stream", done => { + const deflater = zlib.createDeflate(opts); + + const chunk1 = file.slice(0, chunkSize); + const chunk2 = file.slice(chunkSize); + const blkhdr = Buffer.from([0x00, 0x5a, 0x82, 0xa5, 0x7d]); + const blkftr = Buffer.from("010000ffff7dac3072", "hex"); + const expected = Buffer.concat([blkhdr, chunk2, blkftr]); + const bufs = []; + + function read() { + let buf; + while ((buf = deflater.read()) !== null) { + bufs.push(buf); + } + } + + deflater.write(chunk1, () => { + deflater.params(0, zlib.constants.Z_DEFAULT_STRATEGY, () => { + while (deflater.read()); + + deflater.on("readable", read); + + deflater.end(chunk2); + }); + while (deflater.read()); + }); + + deflater.on("end", () => { + const actual = Buffer.concat(bufs); + expect(actual).toEqual(expected); + done(); + }); +}); + +//<#END_FILE: test-zlib-params.js diff --git a/test/js/node/test/parallel/zlib-premature-end.test.js b/test/js/node/test/parallel/zlib-premature-end.test.js new file mode 100644 index 0000000000..cb20184ac0 --- /dev/null +++ b/test/js/node/test/parallel/zlib-premature-end.test.js @@ -0,0 +1,59 @@ +//#FILE: test-zlib-premature-end.js +//#SHA1: f44e08e4886032467eb9cf64412c7eda2b575a16 +//----------------- +"use strict"; +const zlib = require("zlib"); + +const input = "0123456789".repeat(4); + +const testCases = [ + [zlib.deflateRawSync, zlib.createInflateRaw], + [zlib.deflateSync, zlib.createInflate], + [zlib.brotliCompressSync, zlib.createBrotliDecompress], +]; + +const variants = [ + (stream, compressed, trailingData) => { + stream.end(compressed); + }, + (stream, compressed, trailingData) => { + stream.write(compressed); + stream.write(trailingData); + }, + (stream, compressed, trailingData) => { + stream.write(compressed); + stream.end(trailingData); + }, + (stream, compressed, trailingData) => { + stream.write(Buffer.concat([compressed, trailingData])); + }, + (stream, compressed, trailingData) => { + stream.end(Buffer.concat([compressed, trailingData])); + }, +]; + +describe("zlib premature end tests", () => { + testCases.forEach(([compress, decompressor]) => { + describe(`${compress.name} and ${decompressor.name}`, () => { + const compressed = compress(input); + const trailingData = Buffer.from("not valid compressed data"); + + variants.forEach((variant, index) => { + test(`variant ${index + 1}`, done => { + let output = ""; + const stream = decompressor(); + stream.setEncoding("utf8"); + stream.on("data", chunk => (output += chunk)); + stream.on("end", () => { + expect(output).toBe(input); + expect(stream.bytesWritten).toBe(compressed.length); + done(); + }); + variant(stream, compressed, trailingData); + }); + }); + }); + }); +}); + +//<#END_FILE: test-zlib-premature-end.js diff --git a/test/js/node/test/parallel/zlib-reset-before-write.test.js b/test/js/node/test/parallel/zlib-reset-before-write.test.js new file mode 100644 index 0000000000..fd997b8a51 --- /dev/null +++ b/test/js/node/test/parallel/zlib-reset-before-write.test.js @@ -0,0 +1,46 @@ +//#FILE: test-zlib-reset-before-write.js +//#SHA1: 44561d35a5b7e4fc363d7dbde7ec6891af1f338a +//----------------- +"use strict"; +const zlib = require("zlib"); + +// Tests that zlib streams support .reset() and .params() +// before the first write. That is important to ensure that +// lazy init of zlib native library handles these cases. + +const testCases = [ + (z, cb) => { + z.reset(); + cb(); + }, + (z, cb) => z.params(0, zlib.constants.Z_DEFAULT_STRATEGY, cb), +]; + +testCases.forEach((fn, index) => { + test(`zlib stream supports ${index === 0 ? ".reset()" : ".params()"} before first write`, done => { + const deflate = zlib.createDeflate(); + const inflate = zlib.createInflate(); + + deflate.pipe(inflate); + + const output = []; + inflate + .on("error", err => { + expect(err).toBeFalsy(); + }) + .on("data", chunk => output.push(chunk)) + .on("end", () => { + expect(Buffer.concat(output).toString()).toBe("abc"); + done(); + }); + + fn(deflate, () => { + fn(inflate, () => { + deflate.write("abc"); + deflate.end(); + }); + }); + }); +}); + +//<#END_FILE: test-zlib-reset-before-write.js diff --git a/test/js/node/test/parallel/zlib-write-after-close.test.js b/test/js/node/test/parallel/zlib-write-after-close.test.js new file mode 100644 index 0000000000..a778b7731c --- /dev/null +++ b/test/js/node/test/parallel/zlib-write-after-close.test.js @@ -0,0 +1,52 @@ +//#FILE: test-zlib-write-after-close.js +//#SHA1: 5e09b22ace4e546969c9d31f686446adda15fbd2 +//----------------- +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +"use strict"; + +const zlib = require("node:zlib"); + +test("zlib should not allow writing after close", async () => { + const closeCallback = jest.fn(); + + await new Promise(resolve => { + zlib.gzip("hello", function () { + const unzip = zlib.createGunzip(); + unzip.close(closeCallback); + unzip.write("asd", function (err) { + expect(err).toEqual( + expect.objectContaining({ + code: "ERR_STREAM_DESTROYED", + name: "Error", + message: "Cannot call write after a stream was destroyed", + }), + ); + resolve(); + }); + }); + }); + + expect(closeCallback).toHaveBeenCalledTimes(1); +}); + +//<#END_FILE: test-zlib-write-after-close.js diff --git a/test/js/node/zlib/leak.test.ts b/test/js/node/zlib/leak.test.ts new file mode 100644 index 0000000000..4055bb02fa --- /dev/null +++ b/test/js/node/zlib/leak.test.ts @@ -0,0 +1,96 @@ +import { beforeAll, describe, expect, test } from "bun:test"; +import { promisify } from "node:util"; +import zlib from "node:zlib"; + +const input = Buffer.alloc(50000); +for (let i = 0; i < input.length; i++) input[i] = Math.random(); + +describe("zlib compression does not leak memory", () => { + beforeAll(() => { + for (let index = 0; index < 10_000; index++) { + zlib.deflateSync(input); + } + Bun.gc(true); + console.log("beforeAll done"); + }); + + for (const compress of ["deflate", "gzip"] as const) { + test( + compress, + async () => { + for (let index = 0; index < 10_000; index++) { + await promisify(zlib[compress])(input); + } + const baseline = process.memoryUsage.rss(); + console.log(baseline); + for (let index = 0; index < 10_000; index++) { + await promisify(zlib[compress])(input); + } + Bun.gc(true); + const after = process.memoryUsage.rss(); + console.log(after); + console.log("-", after - baseline); + console.log("-", 1024 * 1024 * 10); + expect(after - baseline).toBeLessThan(1024 * 1024 * 10); + }, + 0, + ); + } + + for (const compress of ["deflateSync", "gzipSync"] as const) { + test( + compress, + async () => { + for (let index = 0; index < 10_000; index++) { + zlib[compress](input); + } + const baseline = process.memoryUsage.rss(); + console.log(baseline); + for (let index = 0; index < 10_000; index++) { + zlib[compress](input); + } + Bun.gc(true); + const after = process.memoryUsage.rss(); + console.log(after); + console.log("-", after - baseline); + console.log("-", 1024 * 1024 * 10); + expect(after - baseline).toBeLessThan(1024 * 1024 * 10); + }, + 0, + ); + } + + test("brotliCompress", async () => { + for (let index = 0; index < 1_000; index++) { + await promisify(zlib.brotliCompress)(input); + } + const baseline = process.memoryUsage.rss(); + console.log(baseline); + for (let index = 0; index < 1_000; index++) { + await promisify(zlib.brotliCompress)(input); + } + Bun.gc(true); + const after = process.memoryUsage.rss(); + console.log(after); + console.log("-", after - baseline); + console.log("-", 1024 * 1024 * 10); + expect(after - baseline).toBeLessThan(1024 * 1024 * 10); + }, 0); + + test("brotliCompressSync", async () => { + for (let index = 0; index < 1_000; index++) { + zlib.brotliCompressSync(input); + } + const baseline = process.memoryUsage.rss(); + console.log(baseline); + for (let index = 0; index < 1_000; index++) { + zlib.brotliCompressSync(input); + } + Bun.gc(true); + const after = process.memoryUsage.rss(); + console.log(after); + console.log("-", after - baseline); + console.log("-", 1024 * 1024 * 10); + expect(after - baseline).toBeLessThan(1024 * 1024 * 10); + }, 0); +}); diff --git a/test/js/node/zlib/zlib.test.js b/test/js/node/zlib/zlib.test.js index ce4aa382b3..7dfb652ee8 100644 --- a/test/js/node/zlib/zlib.test.js +++ b/test/js/node/zlib/zlib.test.js @@ -109,32 +109,24 @@ describe("zlib.brotli", () => { expect(roundtrip.toString()).toEqual(inputString); }); - it("can compress streaming", () => { + it("can compress streaming", async () => { const encoder = zlib.createBrotliCompress(); for (const chunk of window(inputString, 55)) { - encoder._transform(chunk, undefined, (err, data) => { - expect(err).toBeUndefined(); - expect(data).toEqual(Buffer(0)); - }); + encoder.push(chunk); } - encoder._flush((err, data) => { - expect(err).toBeUndefined(); - expect(data).toEqual(compressedBuffer); - }); + encoder.push(null); + const buf = await new Response(encoder).text(); + expect(buf).toEqual(inputString); }); - it("can decompress streaming", () => { + it("can decompress streaming", async () => { const decoder = zlib.createBrotliDecompress(); for (const chunk of window(compressedBuffer, 10)) { - decoder._transform(chunk, undefined, (err, data) => { - expect(err).toBeUndefined(); - expect(data).toEqual(Buffer(0)); - }); + decoder.push(chunk); } - decoder._flush((err, data) => { - expect(err).toBeUndefined(); - expect(data).toEqual(Buffer.from(inputString)); - }); + decoder.push(null); + const buf = await new Response(decoder).bytes(); + expect(buf).toEqual(compressedBuffer); }); it("can roundtrip an empty string", async () => { @@ -144,19 +136,15 @@ describe("zlib.brotli", () => { expect(roundtrip.toString()).toEqual(input); }); - it("can compress streaming big", () => { + it("can compress streaming big", async () => { const encoder = zlib.createBrotliCompress(); - // prettier-ignore - for (const chunk of window(inputString+inputString+inputString+inputString, 65)) { - encoder._transform(chunk, undefined, (err, data) => { - expect(err).toBeUndefined(); - expect(data).toEqual(Buffer(0)); - }); + const input = inputString + inputString + inputString + inputString; + for (const chunk of window(input, 65)) { + encoder.push(chunk); } - encoder._flush((err, data) => { - expect(err).toBeUndefined(); - expect(data.length).toBeGreaterThan(0); - }); + encoder.push(null); + const buf = await new Response(encoder).text(); + expect(buf).toEqual(input); }); it("fully works as a stream.Transform", async () => { @@ -315,17 +303,17 @@ for (const [compress, decompressor] of [ stream => { stream.end(compressed); }, - // stream => { - // stream.write(compressed); - // stream.write(trailingData); - // }, + stream => { + stream.write(compressed); + stream.write(trailingData); + }, stream => { stream.write(compressed); stream.end(trailingData); }, - // stream => { - // stream.write(Buffer.concat([compressed, trailingData])); - // }, + stream => { + stream.write(Buffer.concat([compressed, trailingData])); + }, stream => { stream.end(Buffer.concat([compressed, trailingData])); },