diff options
author | 2022-06-22 23:21:48 -0700 | |
---|---|---|
committer | 2022-06-22 23:21:48 -0700 | |
commit | 729d445b6885f69dd2c6355f38707bd42851c791 (patch) | |
tree | f87a7c408929ea3f57bbb7ace380cf869da83c0e /src/javascript/jsc/webcore | |
parent | 25f820c6bf1d8ec6d444ef579cc036b8c0607b75 (diff) | |
download | bun-jarred/rename.tar.gz bun-jarred/rename.tar.zst bun-jarred/rename.zip |
change the directory structurejarred/rename
Diffstat (limited to 'src/javascript/jsc/webcore')
-rw-r--r-- | src/javascript/jsc/webcore/base64.zig | 445 | ||||
-rw-r--r-- | src/javascript/jsc/webcore/encoding.zig | 1247 | ||||
-rw-r--r-- | src/javascript/jsc/webcore/response.zig | 4844 | ||||
-rw-r--r-- | src/javascript/jsc/webcore/streams.zig | 2208 |
4 files changed, 0 insertions, 8744 deletions
diff --git a/src/javascript/jsc/webcore/base64.zig b/src/javascript/jsc/webcore/base64.zig deleted file mode 100644 index 50c1ac68d..000000000 --- a/src/javascript/jsc/webcore/base64.zig +++ /dev/null @@ -1,445 +0,0 @@ -// this is ripped from zig's stdlib -const std = @import("std"); -const assert = std.debug.assert; -const testing = std.testing; -const mem = std.mem; - -pub const Error = error{ - InvalidCharacter, - InvalidPadding, - NoSpaceLeft, -}; - -/// Base64 codecs -pub const Codecs = struct { - alphabet_chars: [64]u8, - pad_char: ?u8, - decoderWithIgnore: fn (ignore: []const u8) Base64DecoderWithIgnore, - Encoder: Base64Encoder, - Decoder: Base64Decoder, -}; - -pub const standard_alphabet_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".*; -fn standardBase64DecoderWithIgnore(ignore: []const u8) Base64DecoderWithIgnore { - return Base64DecoderWithIgnore.init(standard_alphabet_chars, '=', ignore); -} - -/// Standard Base64 codecs, with padding -pub const standard = Codecs{ - .alphabet_chars = standard_alphabet_chars, - .pad_char = '=', - .decoderWithIgnore = standardBase64DecoderWithIgnore, - .Encoder = Base64Encoder.init(standard_alphabet_chars, '='), - .Decoder = Base64Decoder.init(standard_alphabet_chars, '='), -}; - -/// Standard Base64 codecs, without padding -pub const standard_no_pad = Codecs{ - .alphabet_chars = standard_alphabet_chars, - .pad_char = null, - .decoderWithIgnore = standardBase64DecoderWithIgnore, - .Encoder = Base64Encoder.init(standard_alphabet_chars, null), - .Decoder = Base64Decoder.init(standard_alphabet_chars, null), -}; - -pub const url_safe_alphabet_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".*; -fn urlSafeBase64DecoderWithIgnore(ignore: []const u8) Base64DecoderWithIgnore { - return Base64DecoderWithIgnore.init(url_safe_alphabet_chars, null, ignore); -} - -/// URL-safe Base64 codecs, with padding -pub const url_safe = Codecs{ - .alphabet_chars = url_safe_alphabet_chars, - .pad_char = '=', - .decoderWithIgnore = urlSafeBase64DecoderWithIgnore, - .Encoder = Base64Encoder.init(url_safe_alphabet_chars, '='), - .Decoder = Base64Decoder.init(url_safe_alphabet_chars, '='), -}; - -/// URL-safe Base64 codecs, without padding -pub const url_safe_no_pad = Codecs{ - .alphabet_chars = url_safe_alphabet_chars, - .pad_char = null, - .decoderWithIgnore = urlSafeBase64DecoderWithIgnore, - .Encoder = Base64Encoder.init(url_safe_alphabet_chars, null), - .Decoder = Base64Decoder.init(url_safe_alphabet_chars, null), -}; - -pub const standard_pad_char = @compileError("deprecated; use standard.pad_char"); -pub const standard_encoder = @compileError("deprecated; use standard.Encoder"); -pub const standard_decoder = @compileError("deprecated; use standard.Decoder"); - -pub const Base64Encoder = struct { - alphabet_chars: [64]u8, - pad_char: ?u8, - - /// A bunch of assertions, then simply pass the data right through. - pub fn init(alphabet_chars: [64]u8, pad_char: ?u8) Base64Encoder { - assert(alphabet_chars.len == 64); - var char_in_alphabet = [_]bool{false} ** 256; - for (alphabet_chars) |c| { - assert(!char_in_alphabet[c]); - assert(pad_char == null or c != pad_char.?); - char_in_alphabet[c] = true; - } - return Base64Encoder{ - .alphabet_chars = alphabet_chars, - .pad_char = pad_char, - }; - } - - /// Compute the encoded length - pub fn calcSize(encoder: *const Base64Encoder, source_len: usize) usize { - if (encoder.pad_char != null) { - return @divTrunc(source_len + 2, 3) * 4; - } else { - const leftover = source_len % 3; - return @divTrunc(source_len, 3) * 4 + @divTrunc(leftover * 4 + 2, 3); - } - } - - /// dest.len must at least be what you get from ::calcSize. - pub fn encode(encoder: *const Base64Encoder, dest: []u8, source: []const u8) []const u8 { - const out_len = encoder.calcSize(source.len); - assert(dest.len >= out_len); - - var acc: u12 = 0; - var acc_len: u4 = 0; - var out_idx: usize = 0; - for (source) |v| { - acc = (acc << 8) + v; - acc_len += 8; - while (acc_len >= 6) { - acc_len -= 6; - dest[out_idx] = encoder.alphabet_chars[@truncate(u6, (acc >> acc_len))]; - out_idx += 1; - } - } - if (acc_len > 0) { - dest[out_idx] = encoder.alphabet_chars[@truncate(u6, (acc << 6 - acc_len))]; - out_idx += 1; - } - if (encoder.pad_char) |pad_char| { - for (dest[out_idx..]) |*pad| { - pad.* = pad_char; - } - } - return dest[0..out_len]; - } -}; - -pub const Base64Decoder = struct { - const invalid_char: u8 = 0xff; - - /// e.g. 'A' => 0. - /// `invalid_char` for any value not in the 64 alphabet chars. - char_to_index: [256]u8, - pad_char: ?u8, - - pub fn init(alphabet_chars: [64]u8, pad_char: ?u8) Base64Decoder { - var result = Base64Decoder{ - .char_to_index = [_]u8{invalid_char} ** 256, - .pad_char = pad_char, - }; - - var char_in_alphabet = [_]bool{false} ** 256; - for (alphabet_chars) |c, i| { - assert(!char_in_alphabet[c]); - assert(pad_char == null or c != pad_char.?); - - result.char_to_index[c] = @intCast(u8, i); - char_in_alphabet[c] = true; - } - return result; - } - - /// Return the maximum possible decoded size for a given input length - The actual length may be less if the input includes padding. - /// `InvalidPadding` is returned if the input length is not valid. - pub fn calcSizeUpperBound(decoder: *const Base64Decoder, source_len: usize) Error!usize { - var result = source_len / 4 * 3; - const leftover = source_len % 4; - if (decoder.pad_char != null) { - if (leftover % 4 != 0) return error.InvalidPadding; - } else { - if (leftover % 4 == 1) return error.InvalidPadding; - result += leftover * 3 / 4; - } - return result; - } - - /// Return the exact decoded size for a slice. - /// `InvalidPadding` is returned if the input length is not valid. - pub fn calcSizeForSlice(decoder: *const Base64Decoder, source: []const u8) Error!usize { - const source_len = source.len; - var result = try decoder.calcSizeUpperBound(source_len); - if (decoder.pad_char) |pad_char| { - if (source_len >= 1 and source[source_len - 1] == pad_char) result -= 1; - if (source_len >= 2 and source[source_len - 2] == pad_char) result -= 1; - } - return result; - } - - /// dest.len must be what you get from ::calcSize. - /// invalid characters result in error.InvalidCharacter. - /// invalid padding results in error.InvalidPadding. - pub fn decode(decoder: *const Base64Decoder, dest: []u8, source: []const u8) Error!void { - if (decoder.pad_char != null and source.len % 4 != 0) return error.InvalidPadding; - var acc: u12 = 0; - var acc_len: u4 = 0; - var dest_idx: usize = 0; - var leftover_idx: ?usize = null; - for (source) |c, src_idx| { - const d = decoder.char_to_index[c]; - if (d == invalid_char) { - if (decoder.pad_char == null or c != decoder.pad_char.?) return error.InvalidCharacter; - leftover_idx = src_idx; - break; - } - acc = (acc << 6) + d; - acc_len += 6; - if (acc_len >= 8) { - acc_len -= 8; - dest[dest_idx] = @truncate(u8, acc >> acc_len); - dest_idx += 1; - } - } - if (acc_len > 4 or (acc & (@as(u12, 1) << acc_len) - 1) != 0) { - return error.InvalidPadding; - } - if (leftover_idx == null) return; - var leftover = source[leftover_idx.?..]; - if (decoder.pad_char) |pad_char| { - const padding_len = acc_len / 2; - var padding_chars: usize = 0; - for (leftover) |c| { - if (c != pad_char) { - return if (c == Base64Decoder.invalid_char) error.InvalidCharacter else error.InvalidPadding; - } - padding_chars += 1; - } - if (padding_chars != padding_len) return error.InvalidPadding; - } - } -}; - -pub const Base64DecoderWithIgnore = struct { - decoder: Base64Decoder, - char_is_ignored: [256]bool, - - pub fn init(alphabet_chars: [64]u8, pad_char: ?u8, ignore_chars: []const u8) Base64DecoderWithIgnore { - var result = Base64DecoderWithIgnore{ - .decoder = Base64Decoder.init(alphabet_chars, pad_char), - .char_is_ignored = [_]bool{false} ** 256, - }; - for (ignore_chars) |c| { - assert(result.decoder.char_to_index[c] == Base64Decoder.invalid_char); - assert(!result.char_is_ignored[c]); - assert(result.decoder.pad_char != c); - result.char_is_ignored[c] = true; - } - return result; - } - - /// Return the maximum possible decoded size for a given input length - The actual length may be less if the input includes padding - /// `InvalidPadding` is returned if the input length is not valid. - pub fn calcSizeUpperBound(decoder_with_ignore: *const Base64DecoderWithIgnore, source_len: usize) Error!usize { - var result = source_len / 4 * 3; - if (decoder_with_ignore.decoder.pad_char == null) { - const leftover = source_len % 4; - result += leftover * 3 / 4; - } - return result; - } - - /// Invalid characters that are not ignored result in error.InvalidCharacter. - /// Invalid padding results in error.InvalidPadding. - /// Decoding more data than can fit in dest results in error.NoSpaceLeft. See also ::calcSizeUpperBound. - /// Returns the number of bytes written to dest. - pub fn decode(decoder_with_ignore: *const Base64DecoderWithIgnore, dest: []u8, source: []const u8) Error!usize { - const decoder = &decoder_with_ignore.decoder; - var acc: u12 = 0; - var acc_len: u4 = 0; - var dest_idx: usize = 0; - var leftover_idx: ?usize = null; - for (source) |c, src_idx| { - if (decoder_with_ignore.char_is_ignored[c]) continue; - const d = decoder.char_to_index[c]; - if (d == Base64Decoder.invalid_char) { - if (decoder.pad_char == null or c != decoder.pad_char.?) return error.InvalidCharacter; - leftover_idx = src_idx; - break; - } - acc = (acc << 6) + d; - acc_len += 6; - if (acc_len >= 8) { - if (dest_idx == dest.len) return error.NoSpaceLeft; - acc_len -= 8; - dest[dest_idx] = @truncate(u8, acc >> acc_len); - dest_idx += 1; - } - } - if (acc_len > 4 or (acc & (@as(u12, 1) << acc_len) - 1) != 0) { - return error.InvalidPadding; - } - const padding_len = acc_len / 2; - if (leftover_idx == null) { - if (decoder.pad_char != null and padding_len != 0) return error.InvalidPadding; - return dest_idx; - } - var leftover = source[leftover_idx.?..]; - if (decoder.pad_char) |pad_char| { - var padding_chars: usize = 0; - for (leftover) |c| { - if (decoder_with_ignore.char_is_ignored[c]) continue; - if (c != pad_char) { - return if (c == Base64Decoder.invalid_char) error.InvalidCharacter else error.InvalidPadding; - } - padding_chars += 1; - } - if (padding_chars != padding_len) return error.InvalidPadding; - } - return dest_idx; - } -}; - -test "base64" { - @setEvalBranchQuota(8000); - try testBase64(); - comptime try testAllApis(standard, "comptime", "Y29tcHRpbWU="); -} - -test "base64 url_safe_no_pad" { - @setEvalBranchQuota(8000); - try testBase64UrlSafeNoPad(); - comptime try testAllApis(url_safe_no_pad, "comptime", "Y29tcHRpbWU"); -} - -fn testBase64() !void { - const codecs = standard; - - try testAllApis(codecs, "", ""); - try testAllApis(codecs, "f", "Zg=="); - try testAllApis(codecs, "fo", "Zm8="); - try testAllApis(codecs, "foo", "Zm9v"); - try testAllApis(codecs, "foob", "Zm9vYg=="); - try testAllApis(codecs, "fooba", "Zm9vYmE="); - try testAllApis(codecs, "foobar", "Zm9vYmFy"); - - try testDecodeIgnoreSpace(codecs, "", " "); - try testDecodeIgnoreSpace(codecs, "f", "Z g= ="); - try testDecodeIgnoreSpace(codecs, "fo", " Zm8="); - try testDecodeIgnoreSpace(codecs, "foo", "Zm9v "); - try testDecodeIgnoreSpace(codecs, "foob", "Zm9vYg = = "); - try testDecodeIgnoreSpace(codecs, "fooba", "Zm9v YmE="); - try testDecodeIgnoreSpace(codecs, "foobar", " Z m 9 v Y m F y "); - - // test getting some api errors - try testError(codecs, "A", error.InvalidPadding); - try testError(codecs, "AA", error.InvalidPadding); - try testError(codecs, "AAA", error.InvalidPadding); - try testError(codecs, "A..A", error.InvalidCharacter); - try testError(codecs, "AA=A", error.InvalidPadding); - try testError(codecs, "AA/=", error.InvalidPadding); - try testError(codecs, "A/==", error.InvalidPadding); - try testError(codecs, "A===", error.InvalidPadding); - try testError(codecs, "====", error.InvalidPadding); - - try testNoSpaceLeftError(codecs, "AA=="); - try testNoSpaceLeftError(codecs, "AAA="); - try testNoSpaceLeftError(codecs, "AAAA"); - try testNoSpaceLeftError(codecs, "AAAAAA=="); -} - -fn testBase64UrlSafeNoPad() !void { - const codecs = url_safe_no_pad; - - try testAllApis(codecs, "", ""); - try testAllApis(codecs, "f", "Zg"); - try testAllApis(codecs, "fo", "Zm8"); - try testAllApis(codecs, "foo", "Zm9v"); - try testAllApis(codecs, "foob", "Zm9vYg"); - try testAllApis(codecs, "fooba", "Zm9vYmE"); - try testAllApis(codecs, "foobar", "Zm9vYmFy"); - - try testDecodeIgnoreSpace(codecs, "", " "); - try testDecodeIgnoreSpace(codecs, "f", "Z g "); - try testDecodeIgnoreSpace(codecs, "fo", " Zm8"); - try testDecodeIgnoreSpace(codecs, "foo", "Zm9v "); - try testDecodeIgnoreSpace(codecs, "foob", "Zm9vYg "); - try testDecodeIgnoreSpace(codecs, "fooba", "Zm9v YmE"); - try testDecodeIgnoreSpace(codecs, "foobar", " Z m 9 v Y m F y "); - - // test getting some api errors - try testError(codecs, "A", error.InvalidPadding); - try testError(codecs, "AAA=", error.InvalidCharacter); - try testError(codecs, "A..A", error.InvalidCharacter); - try testError(codecs, "AA=A", error.InvalidCharacter); - try testError(codecs, "AA/=", error.InvalidCharacter); - try testError(codecs, "A/==", error.InvalidCharacter); - try testError(codecs, "A===", error.InvalidCharacter); - try testError(codecs, "====", error.InvalidCharacter); - - try testNoSpaceLeftError(codecs, "AA"); - try testNoSpaceLeftError(codecs, "AAA"); - try testNoSpaceLeftError(codecs, "AAAA"); - try testNoSpaceLeftError(codecs, "AAAAAA"); -} - -fn testAllApis(codecs: Codecs, expected_decoded: []const u8, expected_encoded: []const u8) !void { - // Base64Encoder - { - var buffer: [0x100]u8 = undefined; - const encoded = codecs.Encoder.encode(&buffer, expected_decoded); - try testing.expectEqualSlices(u8, expected_encoded, encoded); - } - - // Base64Decoder - { - var buffer: [0x100]u8 = undefined; - var decoded = buffer[0..try codecs.Decoder.calcSizeForSlice(expected_encoded)]; - try codecs.Decoder.decode(decoded, expected_encoded); - try testing.expectEqualSlices(u8, expected_decoded, decoded); - } - - // Base64DecoderWithIgnore - { - const decoder_ignore_nothing = codecs.decoderWithIgnore(""); - var buffer: [0x100]u8 = undefined; - var decoded = buffer[0..try decoder_ignore_nothing.calcSizeUpperBound(expected_encoded.len)]; - var written = try decoder_ignore_nothing.decode(decoded, expected_encoded); - try testing.expect(written <= decoded.len); - try testing.expectEqualSlices(u8, expected_decoded, decoded[0..written]); - } -} - -fn testDecodeIgnoreSpace(codecs: Codecs, expected_decoded: []const u8, encoded: []const u8) !void { - const decoder_ignore_space = codecs.decoderWithIgnore(" "); - var buffer: [0x100]u8 = undefined; - var decoded = buffer[0..try decoder_ignore_space.calcSizeUpperBound(encoded.len)]; - var written = try decoder_ignore_space.decode(decoded, encoded); - try testing.expectEqualSlices(u8, expected_decoded, decoded[0..written]); -} - -fn testError(codecs: Codecs, encoded: []const u8, expected_err: anyerror) !void { - const decoder_ignore_space = codecs.decoderWithIgnore(" "); - var buffer: [0x100]u8 = undefined; - if (codecs.Decoder.calcSizeForSlice(encoded)) |decoded_size| { - var decoded = buffer[0..decoded_size]; - if (codecs.Decoder.decode(decoded, encoded)) |_| { - return error.ExpectedError; - } else |err| if (err != expected_err) return err; - } else |err| if (err != expected_err) return err; - - if (decoder_ignore_space.decode(buffer[0..], encoded)) |_| { - return error.ExpectedError; - } else |err| if (err != expected_err) return err; -} - -fn testNoSpaceLeftError(codecs: Codecs, encoded: []const u8) !void { - const decoder_ignore_space = codecs.decoderWithIgnore(" "); - var buffer: [0x100]u8 = undefined; - var decoded = buffer[0 .. (try codecs.Decoder.calcSizeForSlice(encoded)) - 1]; - if (decoder_ignore_space.decode(decoded, encoded)) |_| { - return error.ExpectedError; - } else |err| if (err != error.NoSpaceLeft) return err; -} diff --git a/src/javascript/jsc/webcore/encoding.zig b/src/javascript/jsc/webcore/encoding.zig deleted file mode 100644 index b168e97ff..000000000 --- a/src/javascript/jsc/webcore/encoding.zig +++ /dev/null @@ -1,1247 +0,0 @@ -const std = @import("std"); -const Api = @import("../../../api/schema.zig").Api; -const RequestContext = @import("../../../http.zig").RequestContext; -const MimeType = @import("../../../http.zig").MimeType; -const ZigURL = @import("../../../url.zig").URL; -const HTTPClient = @import("http"); -const NetworkThread = HTTPClient.NetworkThread; - -const JSC = @import("../../../jsc.zig"); -const js = JSC.C; - -const Method = @import("../../../http/method.zig").Method; - -const ObjectPool = @import("../../../pool.zig").ObjectPool; -const bun = @import("../../../global.zig"); -const Output = @import("../../../global.zig").Output; -const MutableString = @import("../../../global.zig").MutableString; -const strings = @import("../../../global.zig").strings; -const string = @import("../../../global.zig").string; -const default_allocator = @import("../../../global.zig").default_allocator; -const FeatureFlags = @import("../../../global.zig").FeatureFlags; -const ArrayBuffer = @import("../base.zig").ArrayBuffer; -const Properties = @import("../base.zig").Properties; -const NewClass = @import("../base.zig").NewClass; -const d = @import("../base.zig").d; -const castObj = @import("../base.zig").castObj; -const getAllocator = @import("../base.zig").getAllocator; -const JSPrivateDataPtr = @import("../base.zig").JSPrivateDataPtr; -const GetJSPrivateData = @import("../base.zig").GetJSPrivateData; -const Environment = @import("../../../env.zig"); -const ZigString = JSC.ZigString; -const JSInternalPromise = JSC.JSInternalPromise; -const JSPromise = JSC.JSPromise; -const JSValue = JSC.JSValue; -const JSError = JSC.JSError; -const JSGlobalObject = JSC.JSGlobalObject; - -const VirtualMachine = @import("../javascript.zig").VirtualMachine; -const Task = @import("../javascript.zig").Task; - -const picohttp = @import("picohttp"); - -pub const TextEncoder = struct { - filler: u32 = 0, - - const utf8_string: string = "utf-8"; - - pub export fn TextEncoder__encode( - globalThis: *JSGlobalObject, - zig_str: *const ZigString, - ) JSValue { - var ctx = globalThis.ref(); - if (zig_str.is16Bit()) { - var bytes = strings.toUTF8AllocWithType( - default_allocator, - @TypeOf(zig_str.utf16Slice()), - zig_str.utf16Slice(), - ) catch { - return JSC.toInvalidArguments("Out of memory", .{}, ctx); - }; - return ArrayBuffer.fromBytes(bytes, .Uint8Array).toJS(ctx, null); - } else { - // latin1 always has the same length as utf-8 - // so we can use the Gigacage to allocate the buffer - var array = JSC.JSValue.createUninitializedUint8Array(ctx.ptr(), zig_str.len); - var buffer = array.asArrayBuffer(ctx.ptr()) orelse - return JSC.toInvalidArguments("Out of memory", .{}, ctx); - const result = strings.copyLatin1IntoUTF8(buffer.slice(), []const u8, zig_str.slice()); - std.debug.assert(result.written == zig_str.len); - return array; - } - - unreachable; - } - - // This is a fast path for copying a Rope string into a Uint8Array. - // This keeps us from an extra string temporary allocation - const RopeStringEncoder = struct { - globalThis: *JSGlobalObject, - allocator: std.mem.Allocator, - buffer_value: JSC.JSValue, - slice: []u8, - tail: usize = 0, - any_utf16: bool = false, - - pub fn append8(it: *JSC.JSString.Iterator, ptr: [*]const u8, len: u32) callconv(.C) void { - var this = bun.cast(*RopeStringEncoder, it.data.?); - // we use memcpy here instead of encoding - // SIMD only has an impact for long strings - // so in a case like this, the fastest path is to memcpy - // and then later, we can use the SIMD version - @memcpy(this.slice.ptr + this.tail, ptr, len); - this.tail += len; - } - pub fn append16(it: *JSC.JSString.Iterator, _: [*]const u16, _: u32) callconv(.C) void { - var this = bun.cast(*RopeStringEncoder, it.data.?); - this.any_utf16 = true; - it.stop = 1; - return; - } - pub fn write8(it: *JSC.JSString.Iterator, ptr: [*]const u8, len: u32, offset: u32) callconv(.C) void { - var this = bun.cast(*RopeStringEncoder, it.data.?); - // we use memcpy here instead of encoding - // SIMD only has an impact for long strings - // so in a case like this, the fastest path is to memcpy - // and then later, we can use the SIMD version - @memcpy(this.slice.ptr + offset, ptr, len); - } - pub fn write16(it: *JSC.JSString.Iterator, _: [*]const u16, _: u32, _: u32) callconv(.C) void { - var this = bun.cast(*RopeStringEncoder, it.data.?); - this.any_utf16 = true; - it.stop = 1; - return; - } - - pub fn iter(this: *RopeStringEncoder) JSC.JSString.Iterator { - return .{ - .data = this, - .stop = 0, - .append8 = append8, - .append16 = append16, - .write8 = write8, - .write16 = write16, - }; - } - }; - - // This fast path is only suitable for Latin-1 strings. - // It's not suitable for UTF-16 strings, because getting the byteLength is unpredictable - pub export fn TextEncoder__encodeRopeString( - globalThis: *JSGlobalObject, - rope_str: *JSC.JSString, - ) JSValue { - var ctx = globalThis.ref(); - if (comptime Environment.allow_assert) std.debug.assert(rope_str.is8Bit()); - var array = JSC.JSValue.createUninitializedUint8Array(ctx.ptr(), rope_str.length()); - var encoder = RopeStringEncoder{ - .globalThis = globalThis, - .allocator = bun.default_allocator, - .buffer_value = array, - .slice = (array.asArrayBuffer(globalThis) orelse return JSC.JSValue.jsUndefined()).slice(), - }; - var iter = encoder.iter(); - rope_str.iterator(globalThis, &iter); - - if (encoder.any_utf16) { - return JSC.JSValue.jsUndefined(); - } - - if (comptime !bun.FeatureFlags.latin1_is_now_ascii) { - strings.replaceLatin1WithUTF8(encoder.slice); - } - - return array; - } - - const read_key = ZigString.init("read"); - const written_key = ZigString.init("written"); - - pub export fn TextEncoder__encodeInto( - globalThis: *JSC.JSGlobalObject, - input: *const ZigString, - buf_ptr: [*]u8, - buf_len: usize, - ) JSC.JSValue { - var output = buf_ptr[0..buf_len]; - var result: strings.EncodeIntoResult = strings.EncodeIntoResult{ .read = 0, .written = 0 }; - if (input.is16Bit()) { - const utf16_slice = input.utf16Slice(); - result = strings.copyUTF16IntoUTF8(output, @TypeOf(utf16_slice), utf16_slice); - } else { - result = strings.copyLatin1IntoUTF8(output, @TypeOf(input.slice()), input.slice()); - } - return JSC.JSValue.createObject2(globalThis, &read_key, &written_key, JSValue.jsNumber(result.read), JSValue.jsNumber(result.written)); - } -}; - -comptime { - if (!JSC.is_bindgen) { - _ = TextEncoder.TextEncoder__encode; - _ = TextEncoder.TextEncoder__encodeInto; - _ = TextEncoder.TextEncoder__encodeRopeString; - } -} - -/// https://encoding.spec.whatwg.org/encodings.json -pub const EncodingLabel = enum { - @"UTF-8", - @"IBM866", - @"ISO-8859-2", - @"ISO-8859-3", - @"ISO-8859-4", - @"ISO-8859-5", - @"ISO-8859-6", - @"ISO-8859-7", - @"ISO-8859-8", - @"ISO-8859-8-I", - @"ISO-8859-10", - @"ISO-8859-13", - @"ISO-8859-14", - @"ISO-8859-15", - @"ISO-8859-16", - @"KOI8-R", - @"KOI8-U", - @"macintosh", - @"windows-874", - @"windows-1250", - @"windows-1251", - /// Also known as - /// - ASCII - /// - latin1 - @"windows-1252", - @"windows-1253", - @"windows-1254", - @"windows-1255", - @"windows-1256", - @"windows-1257", - @"windows-1258", - @"x-mac-cyrillic", - @"Big5", - @"EUC-JP", - @"ISO-2022-JP", - @"Shift_JIS", - @"EUC-KR", - @"UTF-16BE", - @"UTF-16LE", - @"x-user-defined", - - pub const Map = std.enums.EnumMap(EncodingLabel, string); - pub const label: Map = brk: { - var map = Map.initFull(""); - map.put(EncodingLabel.@"UTF-8", "utf-8"); - map.put(EncodingLabel.@"UTF-16LE", "utf-16le"); - map.put(EncodingLabel.@"windows-1252", "windows-1252"); - break :brk map; - }; - - const utf16_names = [_]string{ - "ucs-2", - "utf-16", - "unicode", - "utf-16le", - "csunicode", - "unicodefeff", - "iso-10646-ucs-2", - }; - - const utf8_names = [_]string{ - "utf8", - "utf-8", - "unicode11utf8", - "unicode20utf8", - "x-unicode20utf8", - "unicode-1-1-utf-8", - }; - - const latin1_names = [_]string{ - "l1", - "ascii", - "cp819", - "cp1252", - "ibm819", - "latin1", - "iso88591", - "us-ascii", - "x-cp1252", - "iso8859-1", - "iso_8859-1", - "iso-8859-1", - "iso-ir-100", - "csisolatin1", - "windows-1252", - "ansi_x3.4-1968", - "iso_8859-1:1987", - }; - - pub const latin1 = EncodingLabel.@"windows-1252"; - - pub fn which(input_: string) ?EncodingLabel { - const input = strings.trim(input_, " \t\r\n"); - const ExactMatcher = strings.ExactSizeMatcher; - const Eight = ExactMatcher(8); - const Sixteen = ExactMatcher(16); - return switch (input.len) { - 1, 0 => null, - 2...8 => switch (Eight.matchLower(input)) { - Eight.case("l1"), - Eight.case("ascii"), - Eight.case("cp819"), - Eight.case("cp1252"), - Eight.case("ibm819"), - Eight.case("latin1"), - Eight.case("iso88591"), - Eight.case("us-ascii"), - Eight.case("x-cp1252"), - => EncodingLabel.latin1, - - Eight.case("ucs-2"), - Eight.case("utf-16"), - Eight.case("unicode"), - Eight.case("utf-16le"), - => EncodingLabel.@"UTF-16LE", - - Eight.case("utf8"), Eight.case("utf-8") => EncodingLabel.@"UTF-8", - else => null, - }, - - 9...16 => switch (Sixteen.matchLower(input)) { - Sixteen.case("iso8859-1"), - Sixteen.case("iso_8859-1"), - Sixteen.case("iso-8859-1"), - Sixteen.case("iso-ir-100"), - Sixteen.case("csisolatin1"), - Sixteen.case("windows-1252"), - Sixteen.case("ansi_x3.4-1968"), - Sixteen.case("iso_8859-1:1987"), - => EncodingLabel.latin1, - - Sixteen.case("unicode11utf8"), - Sixteen.case("unicode20utf8"), - Sixteen.case("x-unicode20utf8"), - => EncodingLabel.@"UTF-8", - - Sixteen.case("csunicode"), - Sixteen.case("unicodefeff"), - Sixteen.case("iso-10646-ucs-2"), - => EncodingLabel.@"UTF-16LE", - - else => null, - }, - else => if (strings.eqlCaseInsensitiveASCII(input, "unicode-1-1-utf-8", true)) - EncodingLabel.@"UTF-8" - else - null, - }; - } -}; - -pub const TextDecoder = struct { - scratch_memory: []u8 = &[_]u8{}, - ignore_bom: bool = false, - fatal: bool = false, - encoding: EncodingLabel = EncodingLabel.utf8, - - pub const Class = NewClass( - TextDecoder, - .{ - .name = "TextDecoder", - }, - .{ - .decode = .{ - .rfn = decode, - }, - }, - .{ - .encoding = .{ - .get = getEncoding, - .readOnly = true, - }, - .ignoreBOM = .{ - .get = getIgnoreBOM, - .set = setIgnoreBOM, - }, - .fatal = .{ - .get = getFatal, - .set = setFatal, - }, - }, - ); - - pub fn getIgnoreBOM( - this: *TextDecoder, - _: js.JSContextRef, - _: js.JSValueRef, - _: js.JSStringRef, - _: js.ExceptionRef, - ) js.JSValueRef { - return JSC.JSValue.jsBoolean(this.ignore_bom).asObjectRef(); - } - pub fn setIgnoreBOM( - this: *TextDecoder, - _: js.JSContextRef, - _: js.JSValueRef, - _: js.JSStringRef, - value: JSC.C.JSValueRef, - _: js.ExceptionRef, - ) bool { - this.ignore_bom = JSValue.fromRef(value).toBoolean(); - return true; - } - pub fn setFatal( - this: *TextDecoder, - _: js.JSContextRef, - _: js.JSValueRef, - _: js.JSStringRef, - value: JSC.C.JSValueRef, - _: js.ExceptionRef, - ) bool { - this.fatal = JSValue.fromRef(value).toBoolean(); - return true; - } - pub fn getFatal( - this: *TextDecoder, - _: js.JSContextRef, - _: js.JSValueRef, - _: js.JSStringRef, - _: js.ExceptionRef, - ) js.JSValueRef { - return JSC.JSValue.jsBoolean(this.fatal).asObjectRef(); - } - - const utf8_string: string = "utf-8"; - pub fn getEncoding( - this: *TextDecoder, - ctx: js.JSContextRef, - _: js.JSValueRef, - _: js.JSStringRef, - _: js.ExceptionRef, - ) js.JSValueRef { - return ZigString.init(EncodingLabel.label.get(this.encoding).?).toValue(ctx.ptr()).asObjectRef(); - } - const Vector16 = std.meta.Vector(16, u16); - const max_16_ascii: Vector16 = @splat(16, @as(u16, 127)); - - fn decodeUTF16WithAlignment( - _: *TextDecoder, - comptime Slice: type, - slice: Slice, - ctx: js.JSContextRef, - ) JSC.C.JSValueRef { - var i: usize = 0; - - while (i < slice.len) { - while (i + strings.ascii_u16_vector_size <= slice.len) { - const vec: strings.AsciiU16Vector = slice[i..][0..strings.ascii_u16_vector_size].*; - if ((@reduce( - .Or, - @bitCast( - strings.AsciiVectorU16U1, - vec > strings.max_u16_ascii, - ) | @bitCast( - strings.AsciiVectorU16U1, - vec < strings.min_u16_ascii, - ), - ) == 0)) { - break; - } - i += strings.ascii_u16_vector_size; - } - while (i < slice.len and slice[i] <= 127) { - i += 1; - } - break; - } - - // is this actually a UTF-16 string that is just ascii? - // we can still allocate as UTF-16 and just copy the bytes - if (i == slice.len) { - if (comptime Slice == []u16) { - return JSC.C.JSValueMakeString(ctx, JSC.C.JSStringCreateWithCharacters(slice.ptr, slice.len)); - } else { - var str = ZigString.init(""); - str.ptr = @ptrCast([*]u8, slice.ptr); - str.len = slice.len; - str.markUTF16(); - return str.toValueGC(ctx.ptr()).asObjectRef(); - } - } - - var buffer = std.ArrayListAlignedUnmanaged(u16, @alignOf(@TypeOf(slice.ptr))){}; - // copy the allocator to reduce the number of threadlocal accesses - const allocator = VirtualMachine.vm.allocator; - buffer.ensureTotalCapacity(allocator, slice.len) catch unreachable; - buffer.items.len = i; - - @memcpy( - std.mem.sliceAsBytes(buffer.items).ptr, - std.mem.sliceAsBytes(slice).ptr, - std.mem.sliceAsBytes(slice[0..i]).len, - ); - - const first_high_surrogate = 0xD800; - const last_high_surrogate = 0xDBFF; - const first_low_surrogate = 0xDC00; - const last_low_surrogate = 0xDFFF; - - var remainder = slice[i..]; - while (remainder.len > 0) { - switch (remainder[0]) { - 0...127 => { - const count: usize = if (strings.firstNonASCII16CheckMin(Slice, remainder, false)) |index| index + 1 else remainder.len; - - buffer.ensureUnusedCapacity(allocator, count) catch unreachable; - - const prev = buffer.items.len; - buffer.items.len += count; - // Since this string is freshly allocated, we know it's not going to overlap - @memcpy( - std.mem.sliceAsBytes(buffer.items[prev..]).ptr, - std.mem.sliceAsBytes(remainder).ptr, - std.mem.sliceAsBytes(remainder[0..count]).len, - ); - remainder = remainder[count..]; - }, - first_high_surrogate...last_high_surrogate => |first| { - if (remainder.len > 1) { - if (remainder[1] >= first_low_surrogate and remainder[1] <= last_low_surrogate) { - buffer.ensureUnusedCapacity(allocator, 2) catch unreachable; - buffer.items.ptr[buffer.items.len] = first; - buffer.items.ptr[buffer.items.len + 1] = remainder[1]; - buffer.items.len += 2; - remainder = remainder[2..]; - continue; - } - } - buffer.ensureUnusedCapacity(allocator, 1) catch unreachable; - buffer.items.ptr[buffer.items.len] = strings.unicode_replacement; - buffer.items.len += 1; - remainder = remainder[1..]; - continue; - }, - - // Is this an unpaired low surrogate or four-digit hex escape? - else => { - buffer.ensureUnusedCapacity(allocator, 1) catch unreachable; - buffer.items.ptr[buffer.items.len] = strings.unicode_replacement; - buffer.items.len += 1; - remainder = remainder[1..]; - }, - } - } - - var full = buffer.toOwnedSlice(allocator); - - var out = ZigString.init(""); - out.ptr = @ptrCast([*]u8, full.ptr); - out.len = full.len; - out.markUTF16(); - return out.toValueGC(ctx.ptr()).asObjectRef(); - } - - pub fn decode( - this: *TextDecoder, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - args: []const js.JSValueRef, - exception: js.ExceptionRef, - ) js.JSValueRef { - const arguments: []const JSC.JSValue = @ptrCast([*]const JSC.JSValue, args.ptr)[0..args.len]; - - if (arguments.len < 1 or arguments[0].isUndefined()) { - return ZigString.Empty.toValue(ctx.ptr()).asObjectRef(); - } - - const array_buffer = arguments[0].asArrayBuffer(ctx.ptr()) orelse { - JSC.throwInvalidArguments("TextDecoder.decode expects an ArrayBuffer or TypedArray", .{}, ctx, exception); - return null; - }; - - if (array_buffer.len == 0) { - return ZigString.Empty.toValue(ctx.ptr()).asObjectRef(); - } - - JSC.C.JSValueProtect(ctx, args[0]); - defer JSC.C.JSValueUnprotect(ctx, args[0]); - - switch (this.encoding) { - EncodingLabel.@"latin1" => { - return ZigString.init(array_buffer.slice()).toValueGC(ctx.ptr()).asObjectRef(); - }, - EncodingLabel.@"UTF-8" => { - const buffer_slice = array_buffer.slice(); - - if (this.fatal) { - if (strings.toUTF16Alloc(default_allocator, buffer_slice, true)) |result_| { - if (result_) |result| { - return ZigString.toExternalU16(result.ptr, result.len, ctx.ptr()).asObjectRef(); - } - } else |err| { - switch (err) { - error.InvalidByteSequence => { - JSC.JSError(default_allocator, "Invalid byte sequence", .{}, ctx, exception); - return null; - }, - error.OutOfMemory => { - JSC.JSError(default_allocator, "Out of memory", .{}, ctx, exception); - return null; - }, - else => { - JSC.JSError(default_allocator, "Unknown error", .{}, ctx, exception); - return null; - }, - } - } - } else { - if (strings.toUTF16Alloc(default_allocator, buffer_slice, false)) |result_| { - if (result_) |result| { - return ZigString.toExternalU16(result.ptr, result.len, ctx.ptr()).asObjectRef(); - } - } else |err| { - switch (err) { - error.OutOfMemory => { - JSC.JSError(default_allocator, "Out of memory", .{}, ctx, exception); - return null; - }, - else => { - JSC.JSError(default_allocator, "Unknown error", .{}, ctx, exception); - return null; - }, - } - } - } - - // Experiment: using mimalloc directly is slightly slower - return ZigString.init(buffer_slice).toValueGC(ctx.ptr()).asObjectRef(); - }, - - EncodingLabel.@"UTF-16LE" => { - if (std.mem.isAligned(@ptrToInt(array_buffer.ptr) + @as(usize, array_buffer.offset), @alignOf([*]u16))) { - return this.decodeUTF16WithAlignment([]u16, array_buffer.asU16(), ctx); - } - - return this.decodeUTF16WithAlignment([]align(1) u16, array_buffer.asU16Unaligned(), ctx); - }, - else => { - JSC.throwInvalidArguments("TextDecoder.decode set to unsupported encoding", .{}, ctx, exception); - return null; - }, - } - } - - pub const Constructor = JSC.NewConstructor(TextDecoder, .{ - .constructor = .{ .rfn = constructor }, - }, .{}); - - pub fn constructor( - ctx: js.JSContextRef, - _: js.JSObjectRef, - args_: []const js.JSValueRef, - exception: js.ExceptionRef, - ) js.JSObjectRef { - var arguments: []const JSC.JSValue = @ptrCast([*]const JSC.JSValue, args_.ptr)[0..args_.len]; - var encoding = EncodingLabel.@"UTF-8"; - if (arguments.len > 0) { - if (!arguments[0].isString()) { - JSC.throwInvalidArguments("TextDecoder(encoding) label is invalid", .{}, ctx, exception); - return null; - } - - var str = arguments[0].toSlice(ctx.ptr(), default_allocator); - defer if (str.allocated) str.deinit(); - encoding = EncodingLabel.which(str.slice()) orelse { - JSC.throwInvalidArguments("Unsupported encoding label \"{s}\"", .{str.slice()}, ctx, exception); - return null; - }; - } - var decoder = getAllocator(ctx).create(TextDecoder) catch unreachable; - decoder.* = TextDecoder{ .encoding = encoding }; - return TextDecoder.Class.make(ctx, decoder); - } -}; - -/// This code is incredibly redundant -/// We have different paths for creaitng a new buffer versus writing into an existing one -/// That's mostly why all the duplication -/// The majority of the business logic here is just shooting it off to the optimized functions -pub const Encoder = struct { - export fn Bun__encoding__writeLatin1AsHex(input: [*]const u8, len: usize, to: [*]u8, to_len: usize) i64 { - return writeU8(input, len, to, to_len, .hex); - } - export fn Bun__encoding__writeLatin1AsASCII(input: [*]const u8, len: usize, to: [*]u8, to_len: usize) i64 { - return writeU8(input, len, to, to_len, .ascii); - } - export fn Bun__encoding__writeLatin1AsURLSafeBase64(input: [*]const u8, len: usize, to: [*]u8, to_len: usize) i64 { - return writeU8(input, len, to, to_len, .base64url); - } - export fn Bun__encoding__writeLatin1AsUTF16(input: [*]const u8, len: usize, to: [*]u8, to_len: usize) i64 { - return writeU8(input, len, to, to_len, .utf16le); - } - export fn Bun__encoding__writeLatin1AsUTF8(input: [*]const u8, len: usize, to: [*]u8, to_len: usize) i64 { - return writeU8(input, len, to, to_len, JSC.Node.Encoding.utf8); - } - export fn Bun__encoding__writeLatin1AsBase64(input: [*]const u8, len: usize, to: [*]u8, to_len: usize) i64 { - return writeU8(input, len, to, to_len, .base64); - } - export fn Bun__encoding__writeUTF16AsBase64(input: [*]const u16, len: usize, to: [*]u8, to_len: usize) i64 { - return writeU16(input, len, to, to_len, .base64); - } - export fn Bun__encoding__writeUTF16AsHex(input: [*]const u16, len: usize, to: [*]u8, to_len: usize) i64 { - return writeU16(input, len, to, to_len, .hex); - } - export fn Bun__encoding__writeUTF16AsURLSafeBase64(input: [*]const u16, len: usize, to: [*]u8, to_len: usize) i64 { - return writeU16(input, len, to, to_len, .base64url); - } - export fn Bun__encoding__writeUTF16AsUTF16(input: [*]const u16, len: usize, to: [*]u8, to_len: usize) i64 { - return writeU16(input, len, to, to_len, JSC.Node.Encoding.utf16le); - } - export fn Bun__encoding__writeUTF16AsUTF8(input: [*]const u16, len: usize, to: [*]u8, to_len: usize) i64 { - return writeU16(input, len, to, to_len, .utf8); - } - export fn Bun__encoding__writeUTF16AsASCII(input: [*]const u8, len: usize, to: [*]u8, to_len: usize) i64 { - return writeU8(input, len, to, to_len, .ascii); - } - - export fn Bun__encoding__byteLengthLatin1AsHex(input: [*]const u8, len: usize) usize { - return byteLengthU8(input, len, .hex); - } - export fn Bun__encoding__byteLengthLatin1AsASCII(input: [*]const u8, len: usize) usize { - return byteLengthU8(input, len, .ascii); - } - export fn Bun__encoding__byteLengthLatin1AsURLSafeBase64(input: [*]const u8, len: usize) usize { - return byteLengthU8(input, len, .base64url); - } - export fn Bun__encoding__byteLengthLatin1AsUTF16(input: [*]const u8, len: usize) usize { - return byteLengthU8(input, len, .utf16le); - } - export fn Bun__encoding__byteLengthLatin1AsUTF8(input: [*]const u8, len: usize) usize { - return byteLengthU8(input, len, .utf8); - } - export fn Bun__encoding__byteLengthLatin1AsBase64(input: [*]const u8, len: usize) usize { - return byteLengthU8(input, len, .base64); - } - export fn Bun__encoding__byteLengthUTF16AsBase64(input: [*]const u16, len: usize) usize { - return byteLengthU16(input, len, .base64); - } - export fn Bun__encoding__byteLengthUTF16AsHex(input: [*]const u16, len: usize) usize { - return byteLengthU16(input, len, .hex); - } - export fn Bun__encoding__byteLengthUTF16AsURLSafeBase64(input: [*]const u16, len: usize) usize { - return byteLengthU16(input, len, .base64url); - } - export fn Bun__encoding__byteLengthUTF16AsUTF16(input: [*]const u16, len: usize) usize { - return byteLengthU16(input, len, .utf16le); - } - export fn Bun__encoding__byteLengthUTF16AsUTF8(input: [*]const u16, len: usize) usize { - return byteLengthU16(input, len, .utf8); - } - export fn Bun__encoding__byteLengthUTF16AsASCII(input: [*]const u8, len: usize) usize { - return byteLengthU8(input, len, .ascii); - } - - export fn Bun__encoding__constructFromLatin1AsHex(globalObject: *JSGlobalObject, input: [*]const u8, len: usize) JSValue { - var slice = constructFromU8(input, len, .hex); - return JSC.JSValue.createBuffer(globalObject, slice, globalObject.bunVM().allocator); - } - export fn Bun__encoding__constructFromLatin1AsASCII(globalObject: *JSGlobalObject, input: [*]const u8, len: usize) JSValue { - var slice = constructFromU8(input, len, .ascii); - return JSC.JSValue.createBuffer(globalObject, slice, globalObject.bunVM().allocator); - } - export fn Bun__encoding__constructFromLatin1AsURLSafeBase64(globalObject: *JSGlobalObject, input: [*]const u8, len: usize) JSValue { - var slice = constructFromU8(input, len, .base64url); - return JSC.JSValue.createBuffer(globalObject, slice, globalObject.bunVM().allocator); - } - export fn Bun__encoding__constructFromLatin1AsUTF16(globalObject: *JSGlobalObject, input: [*]const u8, len: usize) JSValue { - var slice = constructFromU8(input, len, .utf16le); - return JSC.JSValue.createBuffer(globalObject, slice, globalObject.bunVM().allocator); - } - export fn Bun__encoding__constructFromLatin1AsUTF8(globalObject: *JSGlobalObject, input: [*]const u8, len: usize) JSValue { - var slice = constructFromU8(input, len, JSC.Node.Encoding.utf8); - return JSC.JSValue.createBuffer(globalObject, slice, globalObject.bunVM().allocator); - } - export fn Bun__encoding__constructFromLatin1AsBase64(globalObject: *JSGlobalObject, input: [*]const u8, len: usize) JSValue { - var slice = constructFromU8(input, len, .base64); - return JSC.JSValue.createBuffer(globalObject, slice, globalObject.bunVM().allocator); - } - export fn Bun__encoding__constructFromUTF16AsBase64(globalObject: *JSGlobalObject, input: [*]const u16, len: usize) JSValue { - var slice = constructFromU16(input, len, .base64); - return JSC.JSValue.createBuffer(globalObject, slice, globalObject.bunVM().allocator); - } - export fn Bun__encoding__constructFromUTF16AsHex(globalObject: *JSGlobalObject, input: [*]const u16, len: usize) JSValue { - var slice = constructFromU16(input, len, .hex); - return JSC.JSValue.createBuffer(globalObject, slice, globalObject.bunVM().allocator); - } - export fn Bun__encoding__constructFromUTF16AsURLSafeBase64(globalObject: *JSGlobalObject, input: [*]const u16, len: usize) JSValue { - var slice = constructFromU16(input, len, .base64url); - return JSC.JSValue.createBuffer(globalObject, slice, globalObject.bunVM().allocator); - } - export fn Bun__encoding__constructFromUTF16AsUTF16(globalObject: *JSGlobalObject, input: [*]const u16, len: usize) JSValue { - var slice = constructFromU16(input, len, JSC.Node.Encoding.utf16le); - return JSC.JSValue.createBuffer(globalObject, slice, globalObject.bunVM().allocator); - } - export fn Bun__encoding__constructFromUTF16AsUTF8(globalObject: *JSGlobalObject, input: [*]const u16, len: usize) JSValue { - var slice = constructFromU16(input, len, .utf8); - return JSC.JSValue.createBuffer(globalObject, slice, globalObject.bunVM().allocator); - } - export fn Bun__encoding__constructFromUTF16AsASCII(globalObject: *JSGlobalObject, input: [*]const u16, len: usize) JSValue { - var slice = constructFromU16(input, len, .utf8); - return JSC.JSValue.createBuffer(globalObject, slice, globalObject.bunVM().allocator); - } - - export fn Bun__encoding__toStringUTF16(input: [*]const u8, len: usize, globalObject: *JSC.JSGlobalObject) JSValue { - return toString(input, len, globalObject, JSC.Node.Encoding.utf16le); - } - export fn Bun__encoding__toStringUTF8(input: [*]const u8, len: usize, globalObject: *JSC.JSGlobalObject) JSValue { - return toString(input, len, globalObject, .utf8); - } - export fn Bun__encoding__toStringASCII(input: [*]const u8, len: usize, globalObject: *JSC.JSGlobalObject) JSValue { - return toString(input, len, globalObject, .ascii); - } - export fn Bun__encoding__toStringLatin1(input: [*]const u8, len: usize, globalObject: *JSC.JSGlobalObject) JSValue { - return toString(input, len, globalObject, .latin1); - } - - export fn Bun__encoding__toStringHex(input: [*]const u8, len: usize, globalObject: *JSC.JSGlobalObject) JSValue { - return toString(input, len, globalObject, .hex); - } - - export fn Bun__encoding__toStringBase64(input: [*]const u8, len: usize, globalObject: *JSC.JSGlobalObject) JSValue { - return toString(input, len, globalObject, .base64); - } - - export fn Bun__encoding__toStringURLSafeBase64(input: [*]const u8, len: usize, globalObject: *JSC.JSGlobalObject) JSValue { - return toString(input, len, globalObject, .base64url); - } - - // pub fn writeUTF16AsUTF8(utf16: [*]const u16, len: usize, to: [*]u8, to_len: usize) callconv(.C) i32 { - // return @intCast(i32, strings.copyUTF16IntoUTF8(to[0..to_len], []const u16, utf16[0..len]).written); - // } - - pub fn toString(input_ptr: [*]const u8, len: usize, global: *JSGlobalObject, comptime encoding: JSC.Node.Encoding) JSValue { - if (len == 0) - return ZigString.Empty.toValue(global); - - const input = input_ptr[0..len]; - const allocator = VirtualMachine.vm.allocator; - - switch (comptime encoding) { - .latin1, .ascii => { - var to = allocator.alloc(u8, len) catch return ZigString.init("Out of memory").toErrorInstance(global); - - @memcpy(to.ptr, input_ptr, to.len); - - // Hoping this gets auto vectorized - for (to[0..to.len]) |c, i| { - to[i] = @as(u8, @truncate(u7, c)); - } - - return ZigString.init(to).toExternalValue(global); - }, - .buffer, .utf8 => { - // JSC only supports UTF-16 strings for non-ascii text - const converted = strings.toUTF16Alloc(allocator, input, false) catch return ZigString.init("Out of memory").toErrorInstance(global); - if (converted) |utf16| { - return ZigString.toExternalU16(utf16.ptr, utf16.len, global); - } - - // If we get here, it means we can safely assume the string is 100% ASCII characters - // For this, we rely on the GC to manage the memory to minimize potential for memory leaks - return ZigString.init(input).toValueGC(global); - }, - // potentially convert UTF-16 to UTF-8 - JSC.Node.Encoding.ucs2, JSC.Node.Encoding.utf16le => { - const converted = strings.toUTF16Alloc(allocator, input, false) catch return ZigString.init("Out of memory").toErrorInstance(global); - if (converted) |utf16| { - return ZigString.toExternalU16(utf16.ptr, utf16.len, global); - } - - var output = allocator.alloc(u8, input.len) catch return ZigString.init("Out of memory").toErrorInstance(global); - JSC.WTF.copyLCharsFromUCharSource(output.ptr, []align(1) const u16, @ptrCast([*]align(1) const u16, input.ptr)[0 .. input.len / 2]); - return ZigString.init(output).toExternalValue(global); - }, - - JSC.Node.Encoding.hex => { - var output = allocator.alloc(u8, input.len * 2) catch return ZigString.init("Out of memory").toErrorInstance(global); - const wrote = strings.encodeBytesToHex(output, input); - std.debug.assert(wrote == output.len); - var val = ZigString.init(output); - val.mark(); - return val.toExternalValue(global); - }, - - JSC.Node.Encoding.base64url => { - return JSC.WTF.toBase64URLStringValue(input, global); - }, - - JSC.Node.Encoding.base64 => { - const to_len = bun.base64.encodeLen(input); - var to = allocator.alloc(u8, to_len) catch return ZigString.init("Out of memory").toErrorInstance(global); - const wrote = bun.base64.encode(to, input); - return ZigString.init(to[0..wrote]).toExternalValue(global); - }, - } - } - - pub fn writeU8(input: [*]const u8, len: usize, to: [*]u8, to_len: usize, comptime encoding: JSC.Node.Encoding) i64 { - if (len == 0 or to_len == 0) - return 0; - - // TODO: increase temporary buffer size for larger amounts of data - // defer { - // if (comptime encoding.isBinaryToText()) {} - // } - - // if (comptime encoding.isBinaryToText()) {} - - switch (comptime encoding) { - JSC.Node.Encoding.buffer => { - const written = @minimum(len, to_len); - @memcpy(to, input, written); - - return @intCast(i64, written); - }, - .latin1, .ascii => { - const written = @minimum(len, to_len); - @memcpy(to, input, written); - - // Hoping this gets auto vectorized - for (to[0..written]) |c, i| { - to[i] = @as(u8, @truncate(u7, c)); - } - - return @intCast(i64, written); - }, - .utf8 => { - // need to encode - return @intCast(i64, strings.copyLatin1IntoUTF8(to[0..to_len], []const u8, input[0..len]).written); - }, - // encode latin1 into UTF16 - JSC.Node.Encoding.ucs2, JSC.Node.Encoding.utf16le => { - if (to_len < 2) - return 0; - - if (std.mem.isAligned(@ptrToInt(to), @alignOf([*]u16))) { - var buf = input[0..len]; - var output = @ptrCast([*]u16, @alignCast(@alignOf(u16), to))[0 .. to_len / 2]; - return strings.copyLatin1IntoUTF16([]u16, output, []const u8, buf).written; - } else { - var buf = input[0..len]; - var output = @ptrCast([*]align(1) u16, to)[0 .. to_len / 2]; - return strings.copyLatin1IntoUTF16([]align(1) u16, output, []const u8, buf).written; - } - }, - - JSC.Node.Encoding.hex => { - return @intCast(i64, strings.decodeHexToBytes(to[0..to_len], u8, input[0..len])); - }, - - JSC.Node.Encoding.base64url => { - var slice = strings.trim(input[0..len], "\r\n\t " ++ [_]u8{std.ascii.control_code.VT}); - if (slice.len == 0) - return 0; - - if (strings.eqlComptime(slice[slice.len - 2 ..][0..2], "==")) { - slice = slice[0 .. slice.len - 2]; - } else if (slice[slice.len - 1] == '=') { - slice = slice[0 .. slice.len - 1]; - } - - const wrote = bun.base64.urlsafe.decode(to[0..to_len], slice) catch |err| brk: { - if (err == error.NoSpaceLeft) { - break :brk to_len; - } - - return -1; - }; - return @intCast(i64, wrote); - }, - - JSC.Node.Encoding.base64 => { - var slice = strings.trim(input[0..len], "\r\n\t " ++ [_]u8{std.ascii.control_code.VT}); - var outlen = bun.base64.decodeLen(slice); - - return @intCast(i64, bun.base64.decode(to[0..outlen], slice).written); - }, - // else => return 0, - } - } - - pub fn byteLengthU8(input: [*]const u8, len: usize, comptime encoding: JSC.Node.Encoding) usize { - if (len == 0) - return 0; - - switch (comptime encoding) { - .utf8 => { - return strings.elementLengthLatin1IntoUTF8([]const u8, input[0..len]); - }, - - .latin1, JSC.Node.Encoding.ascii, JSC.Node.Encoding.buffer => { - return len; - }, - - JSC.Node.Encoding.ucs2, JSC.Node.Encoding.utf16le => { - return strings.elementLengthUTF8IntoUTF16([]const u8, input[0..len]) * 2; - }, - - JSC.Node.Encoding.hex => { - return len * 2; - }, - - JSC.Node.Encoding.base64, JSC.Node.Encoding.base64url => { - return bun.base64.encodeLen(input[0..len]); - }, - // else => return &[_]u8{}; - } - } - - pub fn writeU16(input: [*]const u16, len: usize, to: [*]u8, to_len: usize, comptime encoding: JSC.Node.Encoding) i64 { - if (len == 0) - return 0; - - switch (comptime encoding) { - .utf8 => { - return @intCast(i32, strings.copyUTF16IntoUTF8(to[0..to_len], []const u16, input[0..len]).written); - }, - // string is already encoded, just need to copy the data - .latin1, JSC.Node.Encoding.ascii, JSC.Node.Encoding.ucs2, JSC.Node.Encoding.buffer, JSC.Node.Encoding.utf16le => { - strings.copyU16IntoU8(to[0..to_len], []const u16, input[0..len]); - - return @intCast(i64, @minimum(len, to_len)); - }, - - JSC.Node.Encoding.hex => { - return @intCast(i64, strings.decodeHexToBytes(to[0..to_len], u16, input[0..len])); - }, - - JSC.Node.Encoding.base64, JSC.Node.Encoding.base64url => { - if (to_len < 2 or len == 0) - return 0; - - // very very slow case! - // shouldn't really happen though - var transcoded = strings.toUTF8Alloc(bun.default_allocator, input[0..len]) catch return 0; - defer bun.default_allocator.free(transcoded); - return writeU8(transcoded.ptr, transcoded.len, to, to_len, encoding); - }, - // else => return &[_]u8{}; - } - } - - /// Node returns imprecise byte length here - /// Should be fast enough for us to return precise length - pub fn byteLengthU16(input: [*]const u16, len: usize, comptime encoding: JSC.Node.Encoding) usize { - if (len == 0) - return 0; - - switch (comptime encoding) { - // these should be the same size - .ascii, .latin1, .utf8 => { - return strings.elementLengthUTF16IntoUTF8([]const u16, input[0..len]); - }, - JSC.Node.Encoding.ucs2, JSC.Node.Encoding.buffer, JSC.Node.Encoding.utf16le => { - return len * 2; - }, - - JSC.Node.Encoding.hex => { - return len; - }, - - JSC.Node.Encoding.base64, JSC.Node.Encoding.base64url => { - return bun.base64.encodeLen(input[0..len]); - }, - // else => return &[_]u8{}; - } - } - - pub fn constructFromU8(input: [*]const u8, len: usize, comptime encoding: JSC.Node.Encoding) []u8 { - if (len == 0) - return &[_]u8{}; - - const allocator = VirtualMachine.vm.allocator; - - switch (comptime encoding) { - JSC.Node.Encoding.buffer => { - var to = allocator.alloc(u8, len) catch return &[_]u8{}; - @memcpy(to.ptr, input, len); - - return to; - }, - .latin1, .ascii => { - var to = allocator.alloc(u8, len) catch return &[_]u8{}; - @memcpy(to.ptr, input, len); - - // Hoping this gets auto vectorized - for (to[0..len]) |c, i| { - to[i] = @as(u8, @truncate(u7, c)); - } - - return to; - }, - .utf8 => { - // need to encode - return strings.allocateLatin1IntoUTF8(allocator, []const u8, input[0..len]) catch return &[_]u8{}; - }, - // encode latin1 into UTF16 - // return as bytes - JSC.Node.Encoding.ucs2, JSC.Node.Encoding.utf16le => { - var to = allocator.alloc(u16, len) catch return &[_]u8{}; - _ = strings.copyLatin1IntoUTF16([]u16, to, []const u8, input[0..len]); - return std.mem.sliceAsBytes(to[0..len]); - }, - - JSC.Node.Encoding.hex => { - if (len < 2) - return &[_]u8{}; - - var to = allocator.alloc(u8, len / 2) catch return &[_]u8{}; - return to[0..strings.decodeHexToBytes(to, u8, input[0..len])]; - }, - - JSC.Node.Encoding.base64url => { - var slice = strings.trim(input[0..len], "\r\n\t " ++ [_]u8{std.ascii.control_code.VT}); - if (slice.len == 0) - return &[_]u8{}; - - if (strings.eqlComptime(slice[slice.len - 2 ..][0..2], "==")) { - slice = slice[0 .. slice.len - 2]; - } else if (slice[slice.len - 1] == '=') { - slice = slice[0 .. slice.len - 1]; - } - - const to_len = bun.base64.urlsafe.decoder.calcSizeForSlice(slice) catch unreachable; - var to = allocator.alloc(u8, to_len) catch return &[_]u8{}; - - const wrote = bun.base64.urlsafe.decode(to[0..to_len], slice) catch |err| brk: { - if (err == error.NoSpaceLeft) { - break :brk to_len; - } - - return &[_]u8{}; - }; - return to[0..wrote]; - }, - - JSC.Node.Encoding.base64 => { - var slice = strings.trim(input[0..len], "\r\n\t " ++ [_]u8{std.ascii.control_code.VT}); - var outlen = bun.base64.decodeLen(slice); - - var to = allocator.alloc(u8, outlen) catch return &[_]u8{}; - const written = bun.base64.decode(to[0..outlen], slice).written; - return to[0..written]; - }, - // else => return 0, - } - } - - pub fn constructFromU16(input: [*]const u16, len: usize, comptime encoding: JSC.Node.Encoding) []u8 { - if (len == 0) - return &[_]u8{}; - - const allocator = VirtualMachine.vm.allocator; - - switch (comptime encoding) { - .utf8 => { - return strings.toUTF8AllocWithType(allocator, []const u16, input[0..len]) catch return &[_]u8{}; - }, - JSC.Node.Encoding.latin1, JSC.Node.Encoding.buffer, JSC.Node.Encoding.ascii => { - var to = allocator.alloc(u8, len) catch return &[_]u8{}; - @memcpy(to.ptr, input, len); - for (to[0..len]) |c, i| { - to[i] = @as(u8, @truncate(u7, c)); - } - - return to; - }, - // string is already encoded, just need to copy the data - JSC.Node.Encoding.ucs2, JSC.Node.Encoding.utf16le => { - var to = std.mem.sliceAsBytes(allocator.alloc(u16, len * 2) catch return &[_]u8{}); - @memcpy(to.ptr, std.mem.sliceAsBytes(input[0..len]).ptr, std.mem.sliceAsBytes(input[0..len]).len); - return to; - }, - - JSC.Node.Encoding.hex => { - var to = allocator.alloc(u8, len * 2) catch return &[_]u8{}; - return to[0..strings.decodeHexToBytes(to, u16, input[0..len])]; - }, - - JSC.Node.Encoding.base64 => { - - // very very slow case! - // shouldn't really happen though - var transcoded = strings.toUTF8Alloc(allocator, input[0..len]) catch return &[_]u8{}; - defer allocator.free(transcoded); - return constructFromU8(transcoded.ptr, transcoded.len, .base64); - }, - - JSC.Node.Encoding.base64url => { - - // very very slow case! - // shouldn't really happen though - var transcoded = strings.toUTF8Alloc(allocator, input[0..len]) catch return &[_]u8{}; - defer allocator.free(transcoded); - return constructFromU8(transcoded.ptr, transcoded.len, .base64url); - }, - // else => return 0, - } - } - - comptime { - if (!JSC.is_bindgen) { - _ = Bun__encoding__writeLatin1AsHex; - _ = Bun__encoding__writeLatin1AsURLSafeBase64; - _ = Bun__encoding__writeLatin1AsUTF16; - _ = Bun__encoding__writeLatin1AsUTF8; - _ = Bun__encoding__writeLatin1AsBase64; - _ = Bun__encoding__writeUTF16AsBase64; - _ = Bun__encoding__writeUTF16AsHex; - _ = Bun__encoding__writeUTF16AsURLSafeBase64; - _ = Bun__encoding__writeUTF16AsUTF16; - _ = Bun__encoding__writeUTF16AsUTF8; - _ = Bun__encoding__writeLatin1AsASCII; - _ = Bun__encoding__writeUTF16AsASCII; - - _ = Bun__encoding__byteLengthLatin1AsHex; - _ = Bun__encoding__byteLengthLatin1AsURLSafeBase64; - _ = Bun__encoding__byteLengthLatin1AsUTF16; - _ = Bun__encoding__byteLengthLatin1AsUTF8; - _ = Bun__encoding__byteLengthLatin1AsBase64; - _ = Bun__encoding__byteLengthUTF16AsBase64; - _ = Bun__encoding__byteLengthUTF16AsHex; - _ = Bun__encoding__byteLengthUTF16AsURLSafeBase64; - _ = Bun__encoding__byteLengthUTF16AsUTF16; - _ = Bun__encoding__byteLengthUTF16AsUTF8; - _ = Bun__encoding__byteLengthLatin1AsASCII; - _ = Bun__encoding__byteLengthUTF16AsASCII; - - _ = Bun__encoding__toStringUTF16; - _ = Bun__encoding__toStringUTF8; - _ = Bun__encoding__toStringASCII; - _ = Bun__encoding__toStringLatin1; - _ = Bun__encoding__toStringHex; - _ = Bun__encoding__toStringBase64; - _ = Bun__encoding__toStringURLSafeBase64; - - _ = Bun__encoding__constructFromLatin1AsHex; - _ = Bun__encoding__constructFromLatin1AsASCII; - _ = Bun__encoding__constructFromLatin1AsURLSafeBase64; - _ = Bun__encoding__constructFromLatin1AsUTF16; - _ = Bun__encoding__constructFromLatin1AsUTF8; - _ = Bun__encoding__constructFromLatin1AsBase64; - _ = Bun__encoding__constructFromUTF16AsBase64; - _ = Bun__encoding__constructFromUTF16AsHex; - _ = Bun__encoding__constructFromUTF16AsURLSafeBase64; - _ = Bun__encoding__constructFromUTF16AsUTF16; - _ = Bun__encoding__constructFromUTF16AsUTF8; - _ = Bun__encoding__constructFromUTF16AsASCII; - } - } -}; - -comptime { - if (!JSC.is_bindgen) { - std.testing.refAllDecls(Encoder); - } -} - -test "Vec" {} diff --git a/src/javascript/jsc/webcore/response.zig b/src/javascript/jsc/webcore/response.zig deleted file mode 100644 index 75c9f8d98..000000000 --- a/src/javascript/jsc/webcore/response.zig +++ /dev/null @@ -1,4844 +0,0 @@ -const std = @import("std"); -const Api = @import("../../../api/schema.zig").Api; -const bun = @import("../../../global.zig"); -const RequestContext = @import("../../../http.zig").RequestContext; -const MimeType = @import("../../../http.zig").MimeType; -const ZigURL = @import("../../../url.zig").URL; -const HTTPClient = @import("http"); -const NetworkThread = HTTPClient.NetworkThread; -const AsyncIO = NetworkThread.AsyncIO; -const JSC = @import("javascript_core"); -const js = JSC.C; - -const Method = @import("../../../http/method.zig").Method; -const FetchHeaders = JSC.FetchHeaders; -const ObjectPool = @import("../../../pool.zig").ObjectPool; -const SystemError = JSC.SystemError; -const Output = @import("../../../global.zig").Output; -const MutableString = @import("../../../global.zig").MutableString; -const strings = @import("../../../global.zig").strings; -const string = @import("../../../global.zig").string; -const default_allocator = @import("../../../global.zig").default_allocator; -const FeatureFlags = @import("../../../global.zig").FeatureFlags; -const ArrayBuffer = @import("../base.zig").ArrayBuffer; -const Properties = @import("../base.zig").Properties; -const NewClass = @import("../base.zig").NewClass; -const d = @import("../base.zig").d; -const castObj = @import("../base.zig").castObj; -const getAllocator = @import("../base.zig").getAllocator; -const JSPrivateDataPtr = @import("../base.zig").JSPrivateDataPtr; -const GetJSPrivateData = @import("../base.zig").GetJSPrivateData; -const Environment = @import("../../../env.zig"); -const ZigString = JSC.ZigString; -const IdentityContext = @import("../../../identity_context.zig").IdentityContext; -const JSInternalPromise = JSC.JSInternalPromise; -const JSPromise = JSC.JSPromise; -const JSValue = JSC.JSValue; -const JSError = JSC.JSError; -const JSGlobalObject = JSC.JSGlobalObject; - -const VirtualMachine = @import("../javascript.zig").VirtualMachine; -const Task = JSC.Task; -const JSPrinter = @import("../../../js_printer.zig"); -const picohttp = @import("picohttp"); -const StringJoiner = @import("../../../string_joiner.zig"); -const uws = @import("uws"); - -pub const Response = struct { - pub const Pool = struct { - response_objects_pool: [127]JSC.C.JSObjectRef = undefined, - response_objects_used: u8 = 0, - - pub fn get(this: *Pool, ptr: *Response) ?JSC.C.JSObjectRef { - if (comptime JSC.is_bindgen) - unreachable; - if (this.response_objects_used > 0) { - var result = this.response_objects_pool[this.response_objects_used - 1]; - this.response_objects_used -= 1; - if (JSC.C.JSObjectSetPrivate(result, JSPrivateDataPtr.init(ptr).ptr())) { - return result; - } else { - JSC.C.JSValueUnprotect(VirtualMachine.vm.global.ref(), result); - } - } - - return null; - } - - pub fn push(this: *Pool, globalThis: *JSC.JSGlobalObject, object: JSC.JSValue) void { - var remaining = this.response_objects_pool[@minimum(this.response_objects_used, this.response_objects_pool.len)..]; - if (remaining.len == 0) { - JSC.C.JSValueUnprotect(globalThis.ref(), object.asObjectRef()); - return; - } - - if (object.as(Response)) |resp| { - _ = JSC.C.JSObjectSetPrivate(object.asObjectRef(), null); - - _ = resp.body.use(); - resp.finalize(); - remaining[0] = object.asObjectRef(); - this.response_objects_used += 1; - } - } - }; - - pub const Constructor = JSC.NewConstructor( - Response, - .{ - .@"constructor" = constructor, - .@"json" = .{ .rfn = constructJSON }, - .@"redirect" = .{ .rfn = constructRedirect }, - .@"error" = .{ .rfn = constructError }, - }, - .{}, - ); - - pub const Class = NewClass( - Response, - .{ .name = "Response" }, - .{ - .@"finalize" = finalize, - .@"text" = .{ - .rfn = Response.getText, - .ts = d.ts{}, - }, - .@"json" = .{ - .rfn = Response.getJSON, - .ts = d.ts{}, - }, - .@"arrayBuffer" = .{ - .rfn = Response.getArrayBuffer, - .ts = d.ts{}, - }, - .@"blob" = .{ - .rfn = Response.getBlob, - .ts = d.ts{}, - }, - - .@"clone" = .{ - .rfn = doClone, - .ts = d.ts{}, - }, - }, - .{ - .@"url" = .{ - .@"get" = getURL, - .ro = true, - }, - - .@"ok" = .{ - .@"get" = getOK, - .ro = true, - }, - .@"status" = .{ - .@"get" = getStatus, - .ro = true, - }, - .@"statusText" = .{ - .@"get" = getStatusText, - .ro = true, - }, - .@"headers" = .{ - .@"get" = getHeaders, - .ro = true, - }, - .@"bodyUsed" = .{ - .@"get" = getBodyUsed, - .ro = true, - }, - .@"type" = .{ - .@"get" = getResponseType, - .ro = true, - }, - }, - ); - - allocator: std.mem.Allocator, - body: Body, - url: string = "", - status_text: string = "", - redirected: bool = false, - - pub fn getBodyValue( - this: *Response, - ) *Body.Value { - return &this.body.value; - } - - pub inline fn statusCode(this: *const Response) u16 { - return this.body.init.status_code; - } - - pub fn redirectLocation(this: *const Response) ?[]const u8 { - return this.header("location"); - } - - pub fn header(this: *const Response, comptime name: []const u8) ?[]const u8 { - return (this.body.init.headers orelse return null).get(name); - } - - pub const Props = struct {}; - - pub fn writeFormat(this: *const Response, formatter: *JSC.Formatter, writer: anytype, comptime enable_ansi_colors: bool) !void { - const Writer = @TypeOf(writer); - try formatter.writeIndent(Writer, writer); - try writer.print("Response ({}) {{\n", .{bun.fmt.size(this.body.len())}); - { - formatter.indent += 1; - defer formatter.indent -|= 1; - - try formatter.writeIndent(Writer, writer); - try writer.writeAll("ok: "); - formatter.printAs(.Boolean, Writer, writer, JSC.JSValue.jsBoolean(this.isOK()), .BooleanObject, enable_ansi_colors); - formatter.printComma(Writer, writer, enable_ansi_colors) catch unreachable; - try writer.writeAll("\n"); - - try this.body.writeFormat(formatter, writer, enable_ansi_colors); - - formatter.printComma(Writer, writer, enable_ansi_colors) catch unreachable; - try writer.writeAll("\n"); - - try formatter.writeIndent(Writer, writer); - try writer.writeAll("url: \""); - try writer.print(comptime Output.prettyFmt("<r><b>{s}<r>", enable_ansi_colors), .{this.url}); - try writer.writeAll("\""); - formatter.printComma(Writer, writer, enable_ansi_colors) catch unreachable; - try writer.writeAll("\n"); - - try formatter.writeIndent(Writer, writer); - try writer.writeAll("statusText: "); - try JSPrinter.writeJSONString(this.status_text, Writer, writer, false); - formatter.printComma(Writer, writer, enable_ansi_colors) catch unreachable; - try writer.writeAll("\n"); - - try formatter.writeIndent(Writer, writer); - try writer.writeAll("redirected: "); - formatter.printAs(.Boolean, Writer, writer, JSC.JSValue.jsBoolean(this.redirected), .BooleanObject, enable_ansi_colors); - } - try writer.writeAll("\n"); - try formatter.writeIndent(Writer, writer); - try writer.writeAll("}"); - } - - pub fn isOK(this: *const Response) bool { - return this.body.init.status_code == 304 or (this.body.init.status_code >= 200 and this.body.init.status_code <= 299); - } - - pub fn getURL( - this: *Response, - ctx: js.JSContextRef, - _: js.JSValueRef, - _: js.JSStringRef, - _: js.ExceptionRef, - ) js.JSValueRef { - // https://developer.mozilla.org/en-US/docs/Web/API/Response/url - return ZigString.init(this.url).toValueGC(ctx.ptr()).asObjectRef(); - } - - pub fn getResponseType( - this: *Response, - ctx: js.JSContextRef, - _: js.JSValueRef, - _: js.JSStringRef, - _: js.ExceptionRef, - ) js.JSValueRef { - if (this.body.init.status_code < 200) { - return ZigString.init("error").toValue(ctx.ptr()).asObjectRef(); - } - - return ZigString.init("basic").toValue(ctx.ptr()).asObjectRef(); - } - - pub fn getBodyUsed( - this: *Response, - _: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSStringRef, - _: js.ExceptionRef, - ) js.JSValueRef { - return JSC.JSValue.jsBoolean(this.body.value == .Used).asRef(); - } - - pub fn getStatusText( - this: *Response, - ctx: js.JSContextRef, - _: js.JSValueRef, - _: js.JSStringRef, - _: js.ExceptionRef, - ) js.JSValueRef { - // https://developer.mozilla.org/en-US/docs/Web/API/Response/url - return ZigString.init(this.status_text).withEncoding().toValueGC(ctx.ptr()).asObjectRef(); - } - - pub fn getOK( - this: *Response, - ctx: js.JSContextRef, - _: js.JSValueRef, - _: js.JSStringRef, - _: js.ExceptionRef, - ) js.JSValueRef { - // https://developer.mozilla.org/en-US/docs/Web/API/Response/ok - return js.JSValueMakeBoolean(ctx, this.isOK()); - } - - fn getOrCreateHeaders(this: *Response) *FetchHeaders { - if (this.body.init.headers == null) { - this.body.init.headers = FetchHeaders.createEmpty(); - } - return this.body.init.headers.?; - } - - pub fn getHeaders( - this: *Response, - ctx: js.JSContextRef, - _: js.JSValueRef, - _: js.JSStringRef, - _: js.ExceptionRef, - ) js.JSValueRef { - return this.getOrCreateHeaders().toJS(ctx.ptr()).asObjectRef(); - } - - pub fn doClone( - this: *Response, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - _: []const js.JSValueRef, - _: js.ExceptionRef, - ) js.JSValueRef { - var cloned = this.clone(getAllocator(ctx), ctx.ptr()); - var val = Response.makeMaybePooled(ctx, cloned); - if (this.body.init.headers) |headers| { - cloned.body.init.headers = headers.cloneThis(); - } - - return val; - } - - pub fn makeMaybePooled(ctx: js.JSContextRef, ptr: *Response) JSC.C.JSObjectRef { - if (comptime JSC.is_bindgen) - unreachable; - var vm = ctx.bunVM(); - if (vm.response_objects_pool) |pool| { - if (pool.get(ptr)) |object| { - JSC.C.JSValueUnprotect(ctx, object); - return object; - } - } - - return Response.Class.make(ctx, ptr); - } - - pub fn cloneInto( - this: *const Response, - new_response: *Response, - allocator: std.mem.Allocator, - globalThis: *JSGlobalObject, - ) void { - new_response.* = Response{ - .allocator = allocator, - .body = this.body.clone(allocator, globalThis), - .url = allocator.dupe(u8, this.url) catch unreachable, - .status_text = allocator.dupe(u8, this.status_text) catch unreachable, - .redirected = this.redirected, - }; - } - - pub fn clone(this: *const Response, allocator: std.mem.Allocator, globalThis: *JSGlobalObject) *Response { - var new_response = allocator.create(Response) catch unreachable; - this.cloneInto(new_response, allocator, globalThis); - return new_response; - } - - pub usingnamespace BlobInterface(@This()); - - pub fn getStatus( - this: *Response, - ctx: js.JSContextRef, - _: js.JSValueRef, - _: js.JSStringRef, - _: js.ExceptionRef, - ) js.JSValueRef { - // https://developer.mozilla.org/en-US/docs/Web/API/Response/status - return js.JSValueMakeNumber(ctx, @intToFloat(f64, this.body.init.status_code)); - } - - pub fn finalize( - this: *Response, - ) void { - this.body.deinit(this.allocator); - - var allocator = this.allocator; - - if (this.status_text.len > 0) { - allocator.free(this.status_text); - } - - if (this.url.len > 0) { - allocator.free(this.url); - } - - allocator.destroy(this); - } - - pub fn mimeType(response: *const Response, request_ctx_: ?*const RequestContext) string { - return mimeTypeWithDefault(response, MimeType.other, request_ctx_); - } - - pub fn mimeTypeWithDefault(response: *const Response, default: MimeType, request_ctx_: ?*const RequestContext) string { - if (response.header("content-type")) |content_type| { - // Remember, we always lowercase it - // hopefully doesn't matter here tho - return content_type; - } - - if (request_ctx_) |request_ctx| { - if (request_ctx.url.extname.len > 0) { - return MimeType.byExtension(request_ctx.url.extname).value; - } - } - - switch (response.body.value) { - .Blob => |blob| { - if (blob.content_type.len > 0) { - return blob.content_type; - } - - return default.value; - }, - .Used, .Locked, .Empty, .Error => return default.value, - } - } - - pub fn constructJSON( - _: void, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - arguments: []const js.JSValueRef, - _: js.ExceptionRef, - ) js.JSObjectRef { - // https://github.com/remix-run/remix/blob/db2c31f64affb2095e4286b91306b96435967969/packages/remix-server-runtime/responses.ts#L4 - var args = JSC.Node.ArgumentsSlice.from(ctx.bunVM(), arguments); - // var response = getAllocator(ctx).create(Response) catch unreachable; - - var response = Response{ - .body = Body{ - .init = Body.Init{ - .status_code = 200, - }, - .value = Body.Value.empty, - }, - .allocator = getAllocator(ctx), - .url = "", - }; - - const json_value = args.nextEat() orelse JSC.JSValue.zero; - - if (@enumToInt(json_value) != 0) { - var zig_str = JSC.ZigString.init(""); - // calling JSON.stringify on an empty string adds extra quotes - // so this is correct - json_value.jsonStringify(ctx.ptr(), 0, &zig_str); - - if (zig_str.len > 0) { - var zig_str_slice = zig_str.toSlice(getAllocator(ctx)); - - if (zig_str_slice.allocated) { - response.body.value = .{ - .Blob = Blob.initWithAllASCII(zig_str_slice.mut(), zig_str_slice.allocator, ctx.ptr(), false), - }; - } else { - response.body.value = .{ - .Blob = Blob.initWithAllASCII(getAllocator(ctx).dupe(u8, zig_str_slice.slice()) catch unreachable, zig_str_slice.allocator, ctx.ptr(), true), - }; - } - } - } - - if (args.nextEat()) |init| { - if (init.isUndefinedOrNull()) {} else if (init.isNumber()) { - response.body.init.status_code = @intCast(u16, @minimum(@maximum(0, init.toInt32()), std.math.maxInt(u16))); - } else { - if (Body.Init.init(getAllocator(ctx), ctx, init.asObjectRef()) catch null) |_init| { - response.body.init = _init; - } - } - } - - var headers_ref = response.getOrCreateHeaders(); - headers_ref.putDefault("content-type", MimeType.json.value); - var ptr = response.allocator.create(Response) catch unreachable; - ptr.* = response; - - return Response.makeMaybePooled(ctx, ptr); - } - pub fn constructRedirect( - _: void, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - arguments: []const js.JSValueRef, - _: js.ExceptionRef, - ) js.JSObjectRef { - // https://github.com/remix-run/remix/blob/db2c31f64affb2095e4286b91306b96435967969/packages/remix-server-runtime/responses.ts#L4 - var args = JSC.Node.ArgumentsSlice.from(ctx.bunVM(), arguments); - // var response = getAllocator(ctx).create(Response) catch unreachable; - - var response = Response{ - .body = Body{ - .init = Body.Init{ - .status_code = 302, - }, - .value = Body.Value.empty, - }, - .allocator = getAllocator(ctx), - .url = "", - }; - - const url_string_value = args.nextEat() orelse JSC.JSValue.zero; - var url_string = ZigString.init(""); - - if (@enumToInt(url_string_value) != 0) { - url_string = url_string_value.getZigString(ctx.ptr()); - } - var url_string_slice = url_string.toSlice(getAllocator(ctx)); - defer url_string_slice.deinit(); - - if (args.nextEat()) |init| { - if (init.isUndefinedOrNull()) {} else if (init.isNumber()) { - response.body.init.status_code = @intCast(u16, @minimum(@maximum(0, init.toInt32()), std.math.maxInt(u16))); - } else { - if (Body.Init.init(getAllocator(ctx), ctx, init.asObjectRef()) catch null) |_init| { - response.body.init = _init; - } - } - } - - response.body.init.headers = response.getOrCreateHeaders(); - response.body.init.status_code = 302; - var headers_ref = response.body.init.headers.?; - headers_ref.put("location", url_string_slice.slice()); - var ptr = response.allocator.create(Response) catch unreachable; - ptr.* = response; - - return Response.makeMaybePooled(ctx, ptr); - } - pub fn constructError( - _: void, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - _: []const js.JSValueRef, - _: js.ExceptionRef, - ) js.JSObjectRef { - var response = getAllocator(ctx).create(Response) catch unreachable; - response.* = Response{ - .body = Body{ - .init = Body.Init{ - .status_code = 0, - }, - .value = Body.Value.empty, - }, - .allocator = getAllocator(ctx), - .url = "", - }; - - return Response.makeMaybePooled( - ctx, - response, - ); - } - - pub fn constructor( - ctx: js.JSContextRef, - _: js.JSObjectRef, - arguments: []const js.JSValueRef, - exception: js.ExceptionRef, - ) js.JSObjectRef { - const body: Body = brk: { - switch (arguments.len) { - 0 => { - break :brk Body.@"200"(ctx); - }, - 1 => { - break :brk Body.extract(ctx, arguments[0], exception); - }, - else => { - if (js.JSValueGetType(ctx, arguments[1]) == js.JSType.kJSTypeObject) { - break :brk Body.extractWithInit(ctx, arguments[0], arguments[1], exception); - } else { - break :brk Body.extract(ctx, arguments[0], exception); - } - }, - } - unreachable; - }; - - var response = getAllocator(ctx).create(Response) catch unreachable; - response.* = Response{ - .body = body, - .allocator = getAllocator(ctx), - .url = "", - }; - return Response.makeMaybePooled( - ctx, - response, - ); - } -}; - -const null_fd = std.math.maxInt(JSC.Node.FileDescriptor); - -pub const Fetch = struct { - const headers_string = "headers"; - const method_string = "method"; - - var fetch_body_string: MutableString = undefined; - var fetch_body_string_loaded = false; - - const JSType = js.JSType; - - const fetch_error_no_args = "fetch() expects a string but received no arguments."; - const fetch_error_blank_url = "fetch() URL must not be a blank string."; - const JSTypeErrorEnum = std.enums.EnumArray(JSType, string); - const fetch_type_error_names: JSTypeErrorEnum = brk: { - var errors = JSTypeErrorEnum.initUndefined(); - errors.set(JSType.kJSTypeUndefined, "Undefined"); - errors.set(JSType.kJSTypeNull, "Null"); - errors.set(JSType.kJSTypeBoolean, "Boolean"); - errors.set(JSType.kJSTypeNumber, "Number"); - errors.set(JSType.kJSTypeString, "String"); - errors.set(JSType.kJSTypeObject, "Object"); - errors.set(JSType.kJSTypeSymbol, "Symbol"); - break :brk errors; - }; - - const fetch_type_error_string_values = .{ - std.fmt.comptimePrint("fetch() expects a string, but received {s}", .{fetch_type_error_names.get(JSType.kJSTypeUndefined)}), - std.fmt.comptimePrint("fetch() expects a string, but received {s}", .{fetch_type_error_names.get(JSType.kJSTypeNull)}), - std.fmt.comptimePrint("fetch() expects a string, but received {s}", .{fetch_type_error_names.get(JSType.kJSTypeBoolean)}), - std.fmt.comptimePrint("fetch() expects a string, but received {s}", .{fetch_type_error_names.get(JSType.kJSTypeNumber)}), - std.fmt.comptimePrint("fetch() expects a string, but received {s}", .{fetch_type_error_names.get(JSType.kJSTypeString)}), - std.fmt.comptimePrint("fetch() expects a string, but received {s}", .{fetch_type_error_names.get(JSType.kJSTypeObject)}), - std.fmt.comptimePrint("fetch() expects a string, but received {s}", .{fetch_type_error_names.get(JSType.kJSTypeSymbol)}), - }; - - const fetch_type_error_strings: JSTypeErrorEnum = brk: { - var errors = JSTypeErrorEnum.initUndefined(); - errors.set( - JSType.kJSTypeUndefined, - std.mem.span(fetch_type_error_string_values[0]), - ); - errors.set( - JSType.kJSTypeNull, - std.mem.span(fetch_type_error_string_values[1]), - ); - errors.set( - JSType.kJSTypeBoolean, - std.mem.span(fetch_type_error_string_values[2]), - ); - errors.set( - JSType.kJSTypeNumber, - std.mem.span(fetch_type_error_string_values[3]), - ); - errors.set( - JSType.kJSTypeString, - std.mem.span(fetch_type_error_string_values[4]), - ); - errors.set( - JSType.kJSTypeObject, - std.mem.span(fetch_type_error_string_values[5]), - ); - errors.set( - JSType.kJSTypeSymbol, - std.mem.span(fetch_type_error_string_values[6]), - ); - break :brk errors; - }; - - pub const Class = NewClass( - void, - .{ .name = "fetch" }, - .{ - .@"call" = .{ - .rfn = Fetch.call, - .ts = d.ts{}, - }, - }, - .{}, - ); - - pub const FetchTasklet = struct { - promise: *JSInternalPromise = undefined, - http: HTTPClient.AsyncHTTP = undefined, - status: Status = Status.pending, - javascript_vm: *VirtualMachine = undefined, - global_this: *JSGlobalObject = undefined, - - empty_request_body: MutableString = undefined, - // pooled_body: *BodyPool.Node = undefined, - this_object: js.JSObjectRef = null, - resolve: js.JSObjectRef = null, - reject: js.JSObjectRef = null, - context: FetchTaskletContext = undefined, - response_buffer: MutableString = undefined, - - blob_store: ?*Blob.Store = null, - - const Pool = ObjectPool(FetchTasklet, init, true, 32); - const BodyPool = ObjectPool(MutableString, MutableString.init2048, true, 8); - pub const FetchTaskletContext = struct { - tasklet: *FetchTasklet, - }; - - pub fn init(_: std.mem.Allocator) anyerror!FetchTasklet { - return FetchTasklet{}; - } - - pub const Status = enum(u8) { - pending, - running, - done, - }; - - pub fn onDone(this: *FetchTasklet) void { - if (comptime JSC.is_bindgen) - unreachable; - var args = [1]js.JSValueRef{undefined}; - - var callback_object = switch (this.http.state.load(.Monotonic)) { - .success => this.resolve, - .fail => this.reject, - else => unreachable, - }; - - args[0] = switch (this.http.state.load(.Monotonic)) { - .success => this.onResolve().asObjectRef(), - .fail => this.onReject().asObjectRef(), - else => unreachable, - }; - - _ = js.JSObjectCallAsFunction(this.global_this.ref(), callback_object, null, 1, &args, null); - - this.release(); - } - - pub fn reset(_: *FetchTasklet) void {} - - pub fn release(this: *FetchTasklet) void { - js.JSValueUnprotect(this.global_this.ref(), this.resolve); - js.JSValueUnprotect(this.global_this.ref(), this.reject); - js.JSValueUnprotect(this.global_this.ref(), this.this_object); - - this.global_this = undefined; - this.javascript_vm = undefined; - this.promise = undefined; - this.status = Status.pending; - // var pooled = this.pooled_body; - // BodyPool.release(pooled); - // this.pooled_body = undefined; - this.http = undefined; - this.this_object = null; - this.resolve = null; - this.reject = null; - Pool.release(@fieldParentPtr(Pool.Node, "data", this)); - } - - pub const FetchResolver = struct { - pub fn call( - _: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - _: usize, - arguments: [*c]const js.JSValueRef, - _: js.ExceptionRef, - ) callconv(.C) js.JSObjectRef { - return JSPrivateDataPtr.from(js.JSObjectGetPrivate(arguments[0])) - .get(FetchTaskletContext).?.tasklet.onResolve().asObjectRef(); - // return js.JSObjectGetPrivate(arguments[0]).? .tasklet.onResolve().asObjectRef(); - } - }; - - pub const FetchRejecter = struct { - pub fn call( - _: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - _: usize, - arguments: [*c]const js.JSValueRef, - _: js.ExceptionRef, - ) callconv(.C) js.JSObjectRef { - return JSPrivateDataPtr.from(js.JSObjectGetPrivate(arguments[0])) - .get(FetchTaskletContext).?.tasklet.onReject().asObjectRef(); - } - }; - - pub fn onReject(this: *FetchTasklet) JSValue { - if (this.blob_store) |store| { - store.deref(); - } - const fetch_error = std.fmt.allocPrint( - default_allocator, - "fetch() failed – {s}\nurl: \"{s}\"", - .{ - @errorName(this.http.err orelse error.HTTPFail), - this.http.url.href, - }, - ) catch unreachable; - return ZigString.init(fetch_error).toErrorInstance(this.global_this); - } - - pub fn onResolve(this: *FetchTasklet) JSValue { - var allocator = default_allocator; - var http_response = this.http.response.?; - var response = allocator.create(Response) catch unreachable; - if (this.blob_store) |store| { - store.deref(); - } - response.* = Response{ - .allocator = allocator, - .url = allocator.dupe(u8, this.http.url.href) catch unreachable, - .status_text = allocator.dupe(u8, http_response.status) catch unreachable, - .redirected = this.http.redirect_count > 0, - .body = .{ - .init = .{ - .headers = FetchHeaders.createFromPicoHeaders(this.global_this, http_response.headers), - .status_code = @truncate(u16, http_response.status_code), - }, - .value = .{ - .Blob = Blob.init(this.http.response_buffer.toOwnedSliceLeaky(), allocator, this.global_this), - }, - }, - }; - return JSValue.fromRef(Response.makeMaybePooled(@ptrCast(js.JSContextRef, this.global_this), response)); - } - - pub fn get( - allocator: std.mem.Allocator, - method: Method, - url: ZigURL, - headers: Headers.Entries, - headers_buf: string, - request_body: ?*MutableString, - timeout: usize, - request_body_store: ?*Blob.Store, - ) !*FetchTasklet.Pool.Node { - var linked_list = FetchTasklet.Pool.get(allocator); - linked_list.data.javascript_vm = VirtualMachine.vm; - linked_list.data.empty_request_body = MutableString.init(allocator, 0) catch unreachable; - // linked_list.data.pooled_body = BodyPool.get(allocator); - linked_list.data.blob_store = request_body_store; - linked_list.data.response_buffer = MutableString.initEmpty(allocator); - linked_list.data.http = try HTTPClient.AsyncHTTP.init( - allocator, - method, - url, - headers, - headers_buf, - &linked_list.data.response_buffer, - request_body orelse &linked_list.data.empty_request_body, - - timeout, - ); - linked_list.data.context = .{ .tasklet = &linked_list.data }; - - return linked_list; - } - - pub fn queue( - allocator: std.mem.Allocator, - global: *JSGlobalObject, - method: Method, - url: ZigURL, - headers: Headers.Entries, - headers_buf: string, - request_body: ?*MutableString, - timeout: usize, - request_body_store: ?*Blob.Store, - ) !*FetchTasklet.Pool.Node { - var node = try get(allocator, method, url, headers, headers_buf, request_body, timeout, request_body_store); - node.data.promise = JSInternalPromise.create(global); - - node.data.global_this = global; - node.data.http.callback = callback; - var batch = NetworkThread.Batch{}; - node.data.http.schedule(allocator, &batch); - NetworkThread.global.pool.schedule(batch); - VirtualMachine.vm.active_tasks +|= 1; - return node; - } - - pub fn callback(http_: *HTTPClient.AsyncHTTP) void { - var task: *FetchTasklet = @fieldParentPtr(FetchTasklet, "http", http_); - @atomicStore(Status, &task.status, Status.done, .Monotonic); - task.javascript_vm.eventLoop().enqueueTaskConcurrent(Task.init(task)); - } - }; - - pub fn call( - _: void, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - arguments: []const js.JSValueRef, - exception: js.ExceptionRef, - ) js.JSObjectRef { - var globalThis = ctx.ptr(); - - if (arguments.len == 0) { - const fetch_error = fetch_error_no_args; - return JSPromise.rejectedPromiseValue(globalThis, ZigString.init(fetch_error).toErrorInstance(globalThis)).asRef(); - } - - var headers: ?Headers = null; - var body: MutableString = MutableString.initEmpty(bun.default_allocator); - var method = Method.GET; - var args = JSC.Node.ArgumentsSlice.from(ctx.bunVM(), arguments); - var url: ZigURL = undefined; - var first_arg = args.nextEat().?; - var blob_store: ?*Blob.Store = null; - if (first_arg.isString()) { - var url_zig_str = ZigString.init(""); - JSValue.fromRef(arguments[0]).toZigString(&url_zig_str, globalThis); - var url_str = url_zig_str.slice(); - - if (url_str.len == 0) { - const fetch_error = fetch_error_blank_url; - return JSPromise.rejectedPromiseValue(globalThis, ZigString.init(fetch_error).toErrorInstance(globalThis)).asRef(); - } - - if (url_str[0] == '/') { - url_str = strings.append(getAllocator(ctx), VirtualMachine.vm.bundler.options.origin.origin, url_str) catch unreachable; - } else { - url_str = getAllocator(ctx).dupe(u8, url_str) catch unreachable; - } - - NetworkThread.init() catch @panic("Failed to start network thread"); - url = ZigURL.parse(url_str); - - if (arguments.len >= 2 and js.JSValueIsObject(ctx, arguments[1])) { - var options = JSValue.fromRef(arguments[1]); - if (options.get(ctx.ptr(), "method")) |method_| { - var slice_ = method_.toSlice(ctx.ptr(), getAllocator(ctx)); - defer slice_.deinit(); - method = Method.which(slice_.slice()) orelse .GET; - } - - if (options.get(ctx.ptr(), "headers")) |headers_| { - if (headers_.as(FetchHeaders)) |headers__| { - headers = Headers.from(headers__, bun.default_allocator) catch unreachable; - // TODO: make this one pass - } else if (FetchHeaders.createFromJS(ctx.ptr(), headers_)) |headers__| { - headers = Headers.from(headers__, bun.default_allocator) catch unreachable; - headers__.deref(); - } - } - - if (options.get(ctx.ptr(), "body")) |body__| { - if (Blob.fromJS(ctx.ptr(), body__, true, false)) |new_blob| { - if (new_blob.size > 0) { - body = MutableString{ - .list = std.ArrayListUnmanaged(u8){ - .items = bun.constStrToU8(new_blob.sharedView()), - .capacity = new_blob.size, - }, - .allocator = bun.default_allocator, - }; - blob_store = new_blob.store; - } - // transfer is unnecessary here because this is a new slice - //new_blob.transfer(); - } else |_| { - return JSPromise.rejectedPromiseValue(globalThis, ZigString.init("fetch() received invalid body").toErrorInstance(globalThis)).asRef(); - } - } - } - } else if (first_arg.asCheckLoaded(Request)) |request| { - url = ZigURL.parse(request.url.dupe(getAllocator(ctx)) catch unreachable); - method = request.method; - if (request.headers) |head| { - headers = Headers.from(head, bun.default_allocator) catch unreachable; - } - var blob = request.body.use(); - // TODO: make RequestBody _NOT_ a MutableString - body = MutableString{ - .list = std.ArrayListUnmanaged(u8){ - .items = bun.constStrToU8(blob.sharedView()), - .capacity = bun.constStrToU8(blob.sharedView()).len, - }, - .allocator = blob.allocator orelse bun.default_allocator, - }; - blob_store = blob.store; - } else { - const fetch_error = fetch_type_error_strings.get(js.JSValueGetType(ctx, arguments[0])); - return JSPromise.rejectedPromiseValue(globalThis, ZigString.init(fetch_error).toErrorInstance(globalThis)).asRef(); - } - - var header_entries: Headers.Entries = .{}; - var header_buf: string = ""; - - if (headers) |head| { - header_entries = head.entries; - header_buf = head.buf.items; - } - var resolve = js.JSObjectMakeFunctionWithCallback(ctx, null, Fetch.FetchTasklet.FetchResolver.call); - var reject = js.JSObjectMakeFunctionWithCallback(ctx, null, Fetch.FetchTasklet.FetchRejecter.call); - - js.JSValueProtect(ctx, resolve); - js.JSValueProtect(ctx, reject); - - var request_body: ?*MutableString = null; - if (body.list.items.len > 0) { - var mutable = bun.default_allocator.create(MutableString) catch unreachable; - mutable.* = body; - request_body = mutable; - } - - // var resolve = FetchTasklet.FetchResolver.Class.make(ctx: js.JSContextRef, ptr: *ZigType) - var queued = FetchTasklet.queue( - default_allocator, - globalThis, - method, - url, - header_entries, - header_buf, - request_body, - std.time.ns_per_hour, - blob_store, - ) catch unreachable; - queued.data.this_object = js.JSObjectMake(ctx, null, JSPrivateDataPtr.from(&queued.data.context).ptr()); - js.JSValueProtect(ctx, queued.data.this_object); - - var promise = js.JSObjectMakeDeferredPromise(ctx, &resolve, &reject, exception); - queued.data.reject = reject; - queued.data.resolve = resolve; - - return promise; - // queued.data.promise.create(globalThis: *JSGlobalObject) - } -}; - -// https://developer.mozilla.org/en-US/docs/Web/API/Headers -pub const Headers = struct { - pub usingnamespace HTTPClient.Headers; - entries: Headers.Entries = .{}, - buf: std.ArrayListUnmanaged(u8) = .{}, - allocator: std.mem.Allocator, - - pub fn asStr(this: *const Headers, ptr: Api.StringPointer) []const u8 { - return if (ptr.offset + ptr.length <= this.buf.items.len) - this.buf.items[ptr.offset..][0..ptr.length] - else - ""; - } - - pub fn from(headers_ref: *FetchHeaders, allocator: std.mem.Allocator) !Headers { - var header_count: u32 = 0; - var buf_len: u32 = 0; - headers_ref.count(&header_count, &buf_len); - var headers = Headers{ - .entries = .{}, - .buf = .{}, - .allocator = allocator, - }; - headers.entries.ensureTotalCapacity(allocator, header_count) catch unreachable; - headers.entries.len = header_count; - headers.buf.ensureTotalCapacityPrecise(allocator, buf_len) catch unreachable; - headers.buf.items.len = buf_len; - var sliced = headers.entries.slice(); - var names = sliced.items(.name); - var values = sliced.items(.value); - headers_ref.copyTo(names.ptr, values.ptr, headers.buf.items.ptr); - return headers; - } -}; - -const PathOrBlob = union(enum) { - path: JSC.Node.PathOrFileDescriptor, - blob: Blob, - - pub fn fromJS(ctx: js.JSContextRef, args: *JSC.Node.ArgumentsSlice, exception: js.ExceptionRef) ?PathOrBlob { - if (JSC.Node.PathOrFileDescriptor.fromJS(ctx, args, exception)) |path| { - return PathOrBlob{ .path = .{ - .path = .{ - .string = bun.PathString.init((bun.default_allocator.dupeZ(u8, path.path.slice()) catch unreachable)[0..path.path.slice().len]), - }, - } }; - } - - const arg = args.nextEat() orelse return null; - - if (arg.as(Blob)) |blob| { - return PathOrBlob{ - .blob = blob.dupe(), - }; - } - - return null; - } -}; - -pub const Blob = struct { - size: SizeType = 0, - offset: SizeType = 0, - /// When set, the blob will be freed on finalization callbacks - /// If the blob is contained in Response or Request, this must be null - allocator: ?std.mem.Allocator = null, - store: ?*Store = null, - content_type: string = "", - content_type_allocated: bool = false, - - /// JavaScriptCore strings are either latin1 or UTF-16 - /// When UTF-16, they're nearly always due to non-ascii characters - is_all_ascii: ?bool = null, - - globalThis: *JSGlobalObject = undefined, - - /// Max int of double precision - /// 9 petabytes is probably enough for awhile - /// We want to avoid coercing to a BigInt because that's a heap allocation - /// and it's generally just harder to use - pub const SizeType = u52; - pub const max_size = std.math.maxInt(SizeType); - - const CopyFilePromiseHandler = struct { - promise: *JSPromise, - globalThis: *JSGlobalObject, - pub fn run(handler: *@This(), blob_: Store.CopyFile.ResultType) void { - var promise = handler.promise; - var globalThis = handler.globalThis; - bun.default_allocator.destroy(handler); - var blob = blob_ catch |err| { - var error_string = ZigString.init( - std.fmt.allocPrint(bun.default_allocator, "Failed to write file \"{s}\"", .{std.mem.span(@errorName(err))}) catch unreachable, - ); - error_string.mark(); - - promise.reject(globalThis, error_string.toErrorInstance(globalThis)); - return; - }; - var _blob = bun.default_allocator.create(Blob) catch unreachable; - _blob.* = blob; - _blob.allocator = bun.default_allocator; - promise.resolve( - globalThis, - ); - } - }; - - const WriteFileWaitFromLockedValueTask = struct { - file_blob: Blob, - globalThis: *JSGlobalObject, - promise: *JSPromise, - - pub fn thenWrap(this: *anyopaque, value: *Body.Value) void { - then(bun.cast(*WriteFileWaitFromLockedValueTask, this), value); - } - - pub fn then(this: *WriteFileWaitFromLockedValueTask, value: *Body.Value) void { - var promise = this.promise; - var globalThis = this.globalThis; - var file_blob = this.file_blob; - switch (value.*) { - .Error => |err| { - file_blob.detach(); - _ = value.use(); - bun.default_allocator.destroy(this); - promise.reject(globalThis, err); - }, - .Used => { - file_blob.detach(); - _ = value.use(); - bun.default_allocator.destroy(this); - promise.reject(globalThis, ZigString.init("Body was used after it was consumed").toErrorInstance(globalThis)); - }, - .Empty, .Blob => { - var blob = value.use(); - // TODO: this should be one promise not two! - const new_promise = writeFileWithSourceDestination(globalThis.ref(), &blob, &file_blob); - if (JSC.JSValue.fromRef(new_promise.?).asPromise()) |_promise| { - switch (_promise.status(globalThis.vm())) { - .Pending => { - promise.resolve( - globalThis, - JSC.JSValue.fromRef(new_promise.?), - ); - }, - .Rejected => { - promise.reject(globalThis, _promise.result(globalThis.vm())); - }, - else => { - promise.resolve(globalThis, _promise.result(globalThis.vm())); - }, - } - } else if (JSC.JSValue.fromRef(new_promise.?).asInternalPromise()) |_promise| { - switch (_promise.status(globalThis.vm())) { - .Pending => { - promise.resolve( - globalThis, - JSC.JSValue.fromRef(new_promise.?), - ); - }, - .Rejected => { - promise.reject(globalThis, _promise.result(globalThis.vm())); - }, - else => { - promise.resolve(globalThis, _promise.result(globalThis.vm())); - }, - } - } - - file_blob.detach(); - bun.default_allocator.destroy(this); - }, - .Locked => { - value.Locked.callback = thenWrap; - value.Locked.task = this; - }, - } - } - }; - - pub fn writeFileWithSourceDestination( - ctx: JSC.C.JSContextRef, - source_blob: *Blob, - destination_blob: *Blob, - ) js.JSObjectRef { - const destination_type = std.meta.activeTag(destination_blob.store.?.data); - - // Writing an empty string to a file is a no-op - if (source_blob.store == null) { - destination_blob.detach(); - return JSC.JSPromise.resolvedPromiseValue(ctx.ptr(), JSC.JSValue.jsNumber(0)).asObjectRef(); - } - - const source_type = std.meta.activeTag(source_blob.store.?.data); - - if (destination_type == .file and source_type == .bytes) { - var write_file_promise = bun.default_allocator.create(WriteFilePromise) catch unreachable; - write_file_promise.* = .{ - .promise = JSC.JSPromise.create(ctx.ptr()), - .globalThis = ctx.ptr(), - }; - JSC.C.JSValueProtect(ctx, write_file_promise.promise.asValue(ctx.ptr()).asObjectRef()); - - var file_copier = Store.WriteFile.create( - bun.default_allocator, - destination_blob.*, - source_blob.*, - *WriteFilePromise, - write_file_promise, - WriteFilePromise.run, - ) catch unreachable; - var task = Store.WriteFile.WriteFileTask.createOnJSThread(bun.default_allocator, ctx.ptr(), file_copier) catch unreachable; - task.schedule(); - return write_file_promise.promise.asValue(ctx.ptr()).asObjectRef(); - } - // If this is file <> file, we can just copy the file - else if (destination_type == .file and source_type == .file) { - var file_copier = Store.CopyFile.create( - bun.default_allocator, - destination_blob.store.?, - source_blob.store.?, - - destination_blob.offset, - destination_blob.size, - ctx.ptr(), - ) catch unreachable; - file_copier.schedule(); - return file_copier.promise.asObjectRef(); - } else if (destination_type == .bytes and source_type == .bytes) { - // If this is bytes <> bytes, we can just duplicate it - // this is an edgecase - // it will happen if someone did Bun.write(new Blob([123]), new Blob([456])) - // eventually, this could be like Buffer.concat - var clone = source_blob.dupe(); - clone.allocator = bun.default_allocator; - var cloned = bun.default_allocator.create(Blob) catch unreachable; - cloned.* = clone; - return JSPromise.resolvedPromiseValue(ctx.ptr(), JSC.JSValue.fromRef(Blob.Class.make(ctx, cloned))).asObjectRef(); - } else if (destination_type == .bytes and source_type == .file) { - return JSPromise.resolvedPromiseValue( - ctx.ptr(), - JSC.JSValue.fromRef( - source_blob.getSlice(ctx, undefined, undefined, &.{}, null), - ), - ).asObjectRef(); - } - - unreachable; - } - pub fn writeFile( - _: void, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - arguments: []const js.JSValueRef, - exception: js.ExceptionRef, - ) js.JSObjectRef { - var args = JSC.Node.ArgumentsSlice.from(ctx.bunVM(), arguments); - // accept a path or a blob - var path_or_blob = PathOrBlob.fromJS(ctx, &args, exception) orelse { - exception.* = JSC.toInvalidArguments("Bun.write expects a path, file descriptor or a blob", .{}, ctx).asObjectRef(); - return null; - }; - - // if path_or_blob is a path, convert it into a file blob - var destination_blob: Blob = if (path_or_blob == .path) - Blob.findOrCreateFileFromPath(path_or_blob.path, ctx.ptr()) - else - path_or_blob.blob.dupe(); - - if (destination_blob.store == null) { - exception.* = JSC.toInvalidArguments("Writing to an empty blob is not implemented yet", .{}, ctx).asObjectRef(); - return null; - } - - var data = args.nextEat() orelse { - exception.* = JSC.toInvalidArguments("Bun.write(pathOrFdOrBlob, blob) expects a Blob-y thing to write", .{}, ctx).asObjectRef(); - return null; - }; - - if (data.isUndefinedOrNull() or data.isEmpty()) { - exception.* = JSC.toInvalidArguments("Bun.write(pathOrFdOrBlob, blob) expects a Blob-y thing to write", .{}, ctx).asObjectRef(); - return null; - } - - // TODO: implement a writeev() fast path - var source_blob: Blob = brk: { - if (data.as(Response)) |response| { - switch (response.body.value) { - .Used, .Empty, .Blob => { - break :brk response.body.use(); - }, - .Error => { - destination_blob.detach(); - const err = response.body.value.Error; - JSC.C.JSValueUnprotect(ctx, err.asObjectRef()); - _ = response.body.value.use(); - return JSC.JSPromise.rejectedPromiseValue(ctx.ptr(), err).asObjectRef(); - }, - .Locked => { - var task = bun.default_allocator.create(WriteFileWaitFromLockedValueTask) catch unreachable; - var promise = JSC.JSPromise.create(ctx.ptr()); - task.* = WriteFileWaitFromLockedValueTask{ - .globalThis = ctx.ptr(), - .file_blob = destination_blob, - .promise = promise, - }; - - response.body.value.Locked.task = task; - response.body.value.Locked.callback = WriteFileWaitFromLockedValueTask.thenWrap; - - return promise.asValue(ctx.ptr()).asObjectRef(); - }, - } - } - - if (data.as(Request)) |request| { - switch (request.body) { - .Used, .Empty, .Blob => { - break :brk request.body.use(); - }, - .Error => { - destination_blob.detach(); - const err = request.body.Error; - JSC.C.JSValueUnprotect(ctx, err.asObjectRef()); - _ = request.body.use(); - return JSC.JSPromise.rejectedPromiseValue(ctx.ptr(), err).asObjectRef(); - }, - .Locked => { - var task = bun.default_allocator.create(WriteFileWaitFromLockedValueTask) catch unreachable; - var promise = JSC.JSPromise.create(ctx.ptr()); - task.* = WriteFileWaitFromLockedValueTask{ - .globalThis = ctx.ptr(), - .file_blob = destination_blob, - .promise = promise, - }; - - request.body.Locked.task = task; - request.body.Locked.callback = WriteFileWaitFromLockedValueTask.thenWrap; - - return promise.asValue(ctx.ptr()).asObjectRef(); - }, - } - } - - break :brk Blob.fromJS( - ctx.ptr(), - data, - false, - false, - ) catch |err| { - if (err == error.InvalidArguments) { - exception.* = JSC.toInvalidArguments( - "Expected an Array", - .{}, - ctx, - ).asObjectRef(); - return null; - } - - exception.* = JSC.toInvalidArguments( - "Out of memory", - .{}, - ctx, - ).asObjectRef(); - return null; - }; - }; - - return writeFileWithSourceDestination(ctx, &source_blob, &destination_blob); - } - - pub fn constructFile( - _: void, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - arguments: []const js.JSValueRef, - exception: js.ExceptionRef, - ) js.JSObjectRef { - var args = JSC.Node.ArgumentsSlice.from(ctx.bunVM(), arguments); - defer args.deinit(); - - var path = JSC.Node.PathOrFileDescriptor.fromJS(ctx, &args, exception) orelse { - exception.* = JSC.toInvalidArguments("Expected file path string or file descriptor", .{}, ctx).asObjectRef(); - return js.JSValueMakeUndefined(ctx); - }; - - const blob = Blob.findOrCreateFileFromPath(path, ctx.ptr()); - - var ptr = bun.default_allocator.create(Blob) catch unreachable; - ptr.* = blob; - ptr.allocator = bun.default_allocator; - return Blob.Class.make(ctx, ptr); - } - - pub fn findOrCreateFileFromPath(path_: JSC.Node.PathOrFileDescriptor, globalThis: *JSGlobalObject) Blob { - var path = path_; - var vm = globalThis.bunVM(); - if (vm.getFileBlob(path)) |blob| { - blob.ref(); - return Blob.initWithStore(blob, globalThis); - } - - switch (path) { - .path => { - path.path = .{ - .string = bun.PathString.init( - (bun.default_allocator.dupeZ(u8, path.path.slice()) catch unreachable)[0..path.path.slice().len], - ), - }; - }, - .fd => { - switch (path.fd) { - std.os.STDIN_FILENO => return Blob.initWithStore( - vm.rareData().stdin(), - globalThis, - ), - std.os.STDERR_FILENO => return Blob.initWithStore( - vm.rareData().stderr(), - globalThis, - ), - std.os.STDOUT_FILENO => return Blob.initWithStore( - vm.rareData().stdout(), - globalThis, - ), - else => {}, - } - }, - } - - const result = Blob.initWithStore(Blob.Store.initFile(path, null, bun.default_allocator) catch unreachable, globalThis); - vm.putFileBlob(path, result.store.?) catch unreachable; - return result; - } - - pub const Store = struct { - data: Data, - - mime_type: MimeType = MimeType.other, - ref_count: u32 = 0, - is_all_ascii: ?bool = null, - allocator: std.mem.Allocator, - - pub fn size(this: *const Store) SizeType { - return switch (this.data) { - .bytes => this.data.bytes.len, - .file => Blob.max_size, - }; - } - - pub const Map = std.HashMap(u64, *JSC.WebCore.Blob.Store, IdentityContext(u64), 80); - - pub const Data = union(enum) { - bytes: ByteStore, - file: FileStore, - }; - - pub fn ref(this: *Store) void { - this.ref_count += 1; - } - - pub fn external(ptr: ?*anyopaque, _: ?*anyopaque, _: usize) callconv(.C) void { - if (ptr == null) return; - var this = bun.cast(*Store, ptr); - this.deref(); - } - - pub fn initFile(pathlike: JSC.Node.PathOrFileDescriptor, mime_type: ?HTTPClient.MimeType, allocator: std.mem.Allocator) !*Store { - var store = try allocator.create(Blob.Store); - store.* = .{ - .data = .{ .file = FileStore.init( - pathlike, - mime_type orelse brk: { - if (pathlike == .path) { - const sliced = pathlike.path.slice(); - if (sliced.len > 0) { - var extname = std.fs.path.extension(sliced); - extname = std.mem.trim(u8, extname, "."); - if (HTTPClient.MimeType.byExtensionNoDefault(extname)) |mime| { - break :brk mime; - } - } - } - - break :brk null; - }, - ) }, - .allocator = allocator, - .ref_count = 1, - }; - return store; - } - - pub fn init(bytes: []u8, allocator: std.mem.Allocator) !*Store { - var store = try allocator.create(Blob.Store); - store.* = .{ - .data = .{ .bytes = ByteStore.init(bytes, allocator) }, - .allocator = allocator, - .ref_count = 1, - }; - return store; - } - - pub fn sharedView(this: Store) []u8 { - if (this.data == .bytes) - return this.data.bytes.slice(); - - return &[_]u8{}; - } - - pub fn deref(this: *Blob.Store) void { - this.ref_count -= 1; - if (this.ref_count == 0) { - this.deinit(); - } - } - - pub fn deinit(this: *Blob.Store) void { - switch (this.data) { - .bytes => |*bytes| { - bytes.deinit(); - }, - .file => |file| { - VirtualMachine.vm.removeFileBlob(file.pathlike); - }, - } - - this.allocator.destroy(this); - } - - pub fn fromArrayList(list: std.ArrayListUnmanaged(u8), allocator: std.mem.Allocator) !*Blob.Store { - return try Blob.Store.init(list.items, allocator); - } - - pub fn FileOpenerMixin(comptime This: type) type { - return struct { - const __opener_flags = std.os.O.NONBLOCK | std.os.O.CLOEXEC; - const open_flags_ = if (@hasDecl(This, "open_flags")) - This.open_flags | __opener_flags - else - std.os.O.RDONLY | __opener_flags; - - pub fn getFdMac(this: *This) AsyncIO.OpenError!JSC.Node.FileDescriptor { - var buf: [bun.MAX_PATH_BYTES]u8 = undefined; - var path_string = if (@hasField(This, "file_store")) - this.file_store.pathlike.path - else - this.file_blob.store.?.data.file.pathlike.path; - - var path = path_string.sliceZ(&buf); - - this.opened_fd = switch (JSC.Node.Syscall.open(path, open_flags_, JSC.Node.default_permission)) { - .result => |fd| fd, - .err => |err| { - this.errno = AsyncIO.asError(err.errno); - this.system_error = err.withPath(path_string.slice()).toSystemError(); - - return @errSetCast(AsyncIO.OpenError, this.errno.?); - }, - }; - - return this.opened_fd; - } - - pub fn getFd(this: *This) AsyncIO.OpenError!JSC.Node.FileDescriptor { - if (this.opened_fd != null_fd) { - return this.opened_fd; - } - - if (comptime Environment.isMac) { - return try this.getFdMac(); - } else { - return try this.getFdLinux(); - } - } - - pub fn getFdLinux(this: *This) AsyncIO.OpenError!JSC.Node.FileDescriptor { - var aio = &AsyncIO.global; - - var buf: [bun.MAX_PATH_BYTES]u8 = undefined; - var path_string = if (@hasField(This, "file_store")) - this.file_store.pathlike.path - else - this.file_blob.store.?.data.file.pathlike.path; - - var path = path_string.sliceZ(&buf); - - aio.open( - *This, - this, - onOpen, - &this.open_completion, - path, - open_flags_, - JSC.Node.default_permission, - ); - - suspend { - this.open_frame = @frame().*; - } - - if (this.errno) |errno| { - this.system_error = .{ - .syscall = ZigString.init("open"), - .code = ZigString.init(std.mem.span(@errorName(errno))), - .path = ZigString.init(path_string.slice()), - }; - - return @errSetCast(AsyncIO.OpenError, errno); - } - - return this.opened_fd; - } - - pub fn onOpen(this: *This, completion: *HTTPClient.NetworkThread.Completion, result: AsyncIO.OpenError!JSC.Node.FileDescriptor) void { - this.opened_fd = result catch { - this.errno = AsyncIO.asError(-completion.result); - - if (comptime Environment.isLinux) resume this.open_frame; - return; - }; - - if (comptime Environment.isLinux) resume this.open_frame; - } - }; - } - - pub fn FileCloserMixin(comptime This: type) type { - return struct { - pub fn doClose(this: *This) AsyncIO.CloseError!void { - var aio = &AsyncIO.global; - - aio.close( - *This, - this, - onClose, - &this.close_completion, - this.opened_fd, - ); - this.opened_fd = null_fd; - - suspend { - this.close_frame = @frame().*; - } - - if (@hasField(This, "errno")) { - if (this.errno) |errno| { - return @errSetCast(AsyncIO.CloseError, errno); - } - } - } - - pub fn onClose(this: *This, _: *HTTPClient.NetworkThread.Completion, result: AsyncIO.CloseError!void) void { - result catch |err| { - if (@hasField(This, "errno")) { - this.errno = err; - } - resume this.close_frame; - return; - }; - - resume this.close_frame; - } - }; - } - - pub const ReadFile = struct { - const OpenFrameType = if (Environment.isMac) - void - else - @Frame(ReadFile.getFdLinux); - file_store: FileStore, - byte_store: ByteStore = ByteStore{ .allocator = bun.default_allocator }, - store: ?*Store = null, - offset: SizeType = 0, - max_length: SizeType = Blob.max_size, - open_frame: OpenFrameType = undefined, - read_frame: @Frame(ReadFile.doRead) = undefined, - close_frame: @Frame(ReadFile.doClose) = undefined, - open_completion: HTTPClient.NetworkThread.Completion = undefined, - opened_fd: JSC.Node.FileDescriptor = null_fd, - read_completion: HTTPClient.NetworkThread.Completion = undefined, - read_len: SizeType = 0, - read_off: SizeType = 0, - size: SizeType = 0, - buffer: []u8 = undefined, - runAsyncFrame: @Frame(ReadFile.runAsync) = undefined, - close_completion: HTTPClient.NetworkThread.Completion = undefined, - task: HTTPClient.NetworkThread.Task = undefined, - system_error: ?JSC.SystemError = null, - errno: ?anyerror = null, - onCompleteCtx: *anyopaque = undefined, - onCompleteCallback: OnReadFileCallback = undefined, - - convert_to_byte_blob: bool = false, - - pub const Read = struct { - buf: []u8, - is_temporary: bool = false, - }; - pub const ResultType = SystemError.Maybe(Read); - - pub const OnReadFileCallback = fn (ctx: *anyopaque, bytes: ResultType) void; - - pub usingnamespace FileOpenerMixin(ReadFile); - pub usingnamespace FileCloserMixin(ReadFile); - - pub fn createWithCtx( - allocator: std.mem.Allocator, - store: *Store, - onReadFileContext: *anyopaque, - onCompleteCallback: OnReadFileCallback, - off: SizeType, - max_len: SizeType, - ) !*ReadFile { - var read_file = try allocator.create(ReadFile); - read_file.* = ReadFile{ - .file_store = store.data.file, - .offset = off, - .max_length = max_len, - .store = store, - .onCompleteCtx = onReadFileContext, - .onCompleteCallback = onCompleteCallback, - }; - store.ref(); - return read_file; - } - - pub fn create( - allocator: std.mem.Allocator, - store: *Store, - off: SizeType, - max_len: SizeType, - comptime Context: type, - context: Context, - comptime callback: fn (ctx: Context, bytes: ResultType) void, - ) !*ReadFile { - const Handler = struct { - pub fn run(ptr: *anyopaque, bytes: ResultType) void { - callback(bun.cast(Context, ptr), bytes); - } - }; - - return try ReadFile.createWithCtx(allocator, store, @ptrCast(*anyopaque, context), Handler.run, off, max_len); - } - - pub fn doRead(this: *ReadFile) AsyncIO.ReadError!SizeType { - var aio = &AsyncIO.global; - - var remaining = this.buffer[this.read_off..]; - this.read_len = 0; - aio.read( - *ReadFile, - this, - onRead, - &this.read_completion, - this.opened_fd, - remaining[0..@minimum(remaining.len, this.max_length - this.read_off)], - this.offset + this.read_off, - ); - - suspend { - this.read_frame = @frame().*; - } - - if (this.errno) |errno| { - this.system_error = JSC.SystemError{ - .code = ZigString.init(std.mem.span(@errorName(errno))), - .path = if (this.file_store.pathlike == .path) - ZigString.init(this.file_store.pathlike.path.slice()) - else - ZigString.Empty, - .syscall = ZigString.init("read"), - }; - - return @errSetCast(AsyncIO.ReadError, errno); - } - - return this.read_len; - } - - pub const ReadFileTask = JSC.IOTask(@This()); - - pub fn then(this: *ReadFile, _: *JSC.JSGlobalObject) void { - var cb = this.onCompleteCallback; - var cb_ctx = this.onCompleteCtx; - - if (this.store == null and this.system_error != null) { - var system_error = this.system_error.?; - bun.default_allocator.destroy(this); - cb(cb_ctx, ResultType{ .err = system_error }); - return; - } else if (this.store == null) { - bun.default_allocator.destroy(this); - cb(cb_ctx, ResultType{ .err = SystemError{ - .code = ZigString.init("INTERNAL_ERROR"), - .path = ZigString.Empty, - .message = ZigString.init("assertion failure - store should not be null"), - .syscall = ZigString.init("read"), - } }); - return; - } - var store = this.store.?; - - if (this.convert_to_byte_blob and this.file_store.pathlike == .path) { - VirtualMachine.vm.removeFileBlob(this.file_store.pathlike); - } - - if (this.system_error) |err| { - bun.default_allocator.destroy(this); - store.deref(); - cb(cb_ctx, ResultType{ .err = err }); - return; - } - - var buf = this.buffer; - const is_temporary = !this.convert_to_byte_blob; - if (this.convert_to_byte_blob) { - if (store.data == .bytes) { - bun.default_allocator.free(this.buffer); - buf = store.data.bytes.slice(); - } else if (store.data == .file) { - if (this.file_store.pathlike == .path) { - if (this.file_store.pathlike.path == .string) { - bun.default_allocator.free(this.file_store.pathlike.path.slice()); - } - } - store.data = .{ .bytes = ByteStore.init(buf, bun.default_allocator) }; - } - } - - bun.default_allocator.destroy(this); - - // Attempt to free it as soon as possible - if (store.ref_count > 1) { - store.deref(); - cb(cb_ctx, .{ .result = .{ .buf = buf, .is_temporary = is_temporary } }); - } else { - cb(cb_ctx, .{ .result = .{ .buf = buf, .is_temporary = is_temporary } }); - store.deref(); - } - } - pub fn run(this: *ReadFile, task: *ReadFileTask) void { - var frame = HTTPClient.getAllocator().create(@Frame(runAsync)) catch unreachable; - _ = @asyncCall(std.mem.asBytes(frame), undefined, runAsync, .{ this, task }); - } - - pub fn onRead(this: *ReadFile, completion: *HTTPClient.NetworkThread.Completion, result: AsyncIO.ReadError!usize) void { - this.read_len = @truncate(SizeType, result catch |err| { - if (@hasField(HTTPClient.NetworkThread.Completion, "result")) { - this.errno = AsyncIO.asError(-completion.result); - this.system_error = (JSC.Node.Syscall.Error{ - .errno = @intCast(JSC.Node.Syscall.Error.Int, -completion.result), - .syscall = .read, - }).toSystemError(); - } else { - this.errno = err; - this.system_error = .{ .code = ZigString.init(std.mem.span(@errorName(err))), .syscall = ZigString.init("read") }; - } - this.read_len = 0; - resume this.read_frame; - return; - }); - - resume this.read_frame; - } - - fn runAsync(this: *ReadFile, task: *ReadFileTask) void { - this.runAsync_(); - task.onFinish(); - - suspend { - HTTPClient.getAllocator().destroy(@frame()); - } - } - - fn runAsync_(this: *ReadFile) void { - if (this.file_store.pathlike == .fd) { - this.opened_fd = this.file_store.pathlike.fd; - } - - const fd = this.getFd() catch return; - const needs_close = this.file_store.pathlike == .path and fd != null_fd and fd > 2; - const stat: std.os.Stat = switch (JSC.Node.Syscall.fstat(fd)) { - .result => |result| result, - .err => |err| { - this.errno = AsyncIO.asError(err.errno); - this.system_error = err.toSystemError(); - return; - }, - }; - if (std.os.S.ISDIR(stat.mode)) { - this.errno = error.EISDIR; - this.system_error = JSC.SystemError{ - .code = ZigString.init("EISDIR"), - .path = if (this.file_store.pathlike == .path) - ZigString.init(this.file_store.pathlike.path.slice()) - else - ZigString.Empty, - .message = ZigString.init("Directories cannot be read like files"), - .syscall = ZigString.init("read"), - }; - return; - } - - if (stat.size > 0 and std.os.S.ISREG(stat.mode)) { - this.size = @minimum( - @truncate(SizeType, @intCast(SizeType, @maximum(@intCast(i64, stat.size), 0))), - this.max_length, - ); - // read up to 4k at a time if - // they didn't explicitly set a size and we're reading from something that's not a regular file - } else if (stat.size == 0 and !std.os.S.ISREG(stat.mode)) { - this.size = if (this.max_length == Blob.max_size) - 4096 - else - this.max_length; - } - - if (this.size == 0) { - this.buffer = &[_]u8{}; - this.byte_store = ByteStore.init(this.buffer, bun.default_allocator); - - if (needs_close) { - this.doClose() catch {}; - } - return; - } - - var bytes = bun.default_allocator.alloc(u8, this.size) catch |err| { - this.errno = err; - if (needs_close) { - this.doClose() catch {}; - } - return; - }; - this.buffer = bytes; - this.convert_to_byte_blob = std.os.S.ISREG(stat.mode) and this.file_store.pathlike == .path; - - var remain = bytes; - while (remain.len > 0) { - var read_len = this.doRead() catch { - if (needs_close) { - this.doClose() catch {}; - } - return; - }; - this.read_off += read_len; - if (read_len == 0) break; - remain = remain[read_len..]; - } - - _ = bun.default_allocator.resize(bytes, this.read_off); - this.buffer = bytes[0..this.read_off]; - this.byte_store = ByteStore.init(this.buffer, bun.default_allocator); - } - }; - - pub const WriteFile = struct { - const OpenFrameType = if (Environment.isMac) - void - else - @Frame(WriteFile.getFdLinux); - - file_blob: Blob, - bytes_blob: Blob, - - opened_fd: JSC.Node.FileDescriptor = null_fd, - open_frame: OpenFrameType = undefined, - write_frame: @Frame(WriteFile.doWrite) = undefined, - close_frame: @Frame(WriteFile.doClose) = undefined, - system_error: ?JSC.SystemError = null, - errno: ?anyerror = null, - open_completion: HTTPClient.NetworkThread.Completion = undefined, - - write_completion: HTTPClient.NetworkThread.Completion = undefined, - close_completion: HTTPClient.NetworkThread.Completion = undefined, - task: HTTPClient.NetworkThread.Task = undefined, - - onCompleteCtx: *anyopaque = undefined, - onCompleteCallback: OnWriteFileCallback = undefined, - wrote: usize = 0, - - pub const ResultType = SystemError.Maybe(SizeType); - pub const OnWriteFileCallback = fn (ctx: *anyopaque, count: ResultType) void; - - pub usingnamespace FileOpenerMixin(WriteFile); - pub usingnamespace FileCloserMixin(WriteFile); - - // Do not open with APPEND because we may use pwrite() - pub const open_flags = std.os.O.WRONLY | std.os.O.CREAT | std.os.O.TRUNC; - - pub fn createWithCtx( - allocator: std.mem.Allocator, - file_blob: Blob, - bytes_blob: Blob, - onWriteFileContext: *anyopaque, - onCompleteCallback: OnWriteFileCallback, - ) !*WriteFile { - var read_file = try allocator.create(WriteFile); - read_file.* = WriteFile{ - .file_blob = file_blob, - .bytes_blob = bytes_blob, - .onCompleteCtx = onWriteFileContext, - .onCompleteCallback = onCompleteCallback, - }; - file_blob.store.?.ref(); - bytes_blob.store.?.ref(); - return read_file; - } - - pub fn create( - allocator: std.mem.Allocator, - file_blob: Blob, - bytes_blob: Blob, - comptime Context: type, - context: Context, - comptime callback: fn (ctx: Context, bytes: ResultType) void, - ) !*WriteFile { - const Handler = struct { - pub fn run(ptr: *anyopaque, bytes: ResultType) void { - callback(bun.cast(Context, ptr), bytes); - } - }; - - return try WriteFile.createWithCtx( - allocator, - file_blob, - bytes_blob, - @ptrCast(*anyopaque, context), - Handler.run, - ); - } - - pub fn doWrite( - this: *WriteFile, - buffer: []const u8, - file_offset: u64, - ) AsyncIO.WriteError!SizeType { - var aio = &AsyncIO.global; - this.wrote = 0; - const fd = this.opened_fd; - aio.write( - *WriteFile, - this, - onWrite, - &this.write_completion, - fd, - buffer, - if (fd > 2) file_offset else 0, - ); - - suspend { - this.write_frame = @frame().*; - } - - if (this.errno) |errno| { - this.system_error = this.system_error orelse JSC.SystemError{ - .code = ZigString.init(std.mem.span(@errorName(errno))), - .syscall = ZigString.init("write"), - }; - return @errSetCast(AsyncIO.WriteError, errno); - } - - return @truncate(SizeType, this.wrote); - } - - pub const WriteFileTask = JSC.IOTask(@This()); - - pub fn then(this: *WriteFile, _: *JSC.JSGlobalObject) void { - var cb = this.onCompleteCallback; - var cb_ctx = this.onCompleteCtx; - - this.bytes_blob.store.?.deref(); - this.file_blob.store.?.deref(); - - if (this.system_error) |err| { - bun.default_allocator.destroy(this); - cb(cb_ctx, .{ - .err = err, - }); - return; - } - - const wrote = this.wrote; - bun.default_allocator.destroy(this); - cb(cb_ctx, .{ .result = @truncate(SizeType, wrote) }); - } - pub fn run(this: *WriteFile, task: *WriteFileTask) void { - var frame = HTTPClient.getAllocator().create(@Frame(runAsync)) catch unreachable; - _ = @asyncCall(std.mem.asBytes(frame), undefined, runAsync, .{ this, task }); - } - - fn runAsync(this: *WriteFile, task: *WriteFileTask) void { - this._runAsync(); - task.onFinish(); - suspend { - HTTPClient.getAllocator().destroy(@frame()); - } - } - - pub fn onWrite(this: *WriteFile, _: *HTTPClient.NetworkThread.Completion, result: AsyncIO.WriteError!usize) void { - this.wrote += @truncate(SizeType, result catch |err| { - this.errno = err; - this.wrote = 0; - resume this.write_frame; - return; - }); - - resume this.write_frame; - } - - fn _runAsync(this: *WriteFile) void { - const file = this.file_blob.store.?.data.file; - if (file.pathlike == .fd) { - this.opened_fd = file.pathlike.fd; - } - - const fd = this.getFd() catch return; - const needs_close = file.pathlike == .path and fd > 2; - - var remain = this.bytes_blob.sharedView(); - - var total_written: usize = 0; - var file_offset = this.file_blob.offset; - - const end = - @minimum(this.file_blob.size, remain.len); - - while (remain.len > 0 and total_written < end) { - const wrote_len = this.doWrite(remain, file_offset) catch { - if (needs_close) { - this.doClose() catch {}; - } - this.wrote = @truncate(SizeType, total_written); - return; - }; - remain = remain[wrote_len..]; - total_written += wrote_len; - file_offset += wrote_len; - if (wrote_len == 0) break; - } - - this.wrote = @truncate(SizeType, total_written); - - if (needs_close) { - this.doClose() catch {}; - } - } - }; - - pub const IOWhich = enum { - source, - destination, - both, - }; - - const unsupported_directory_error = SystemError{ - .errno = @intCast(c_int, @enumToInt(bun.C.SystemErrno.EISDIR)), - .message = ZigString.init("That doesn't work on folders"), - .syscall = ZigString.init("fstat"), - }; - const unsupported_non_regular_file_error = SystemError{ - .errno = @intCast(c_int, @enumToInt(bun.C.SystemErrno.ENOTSUP)), - .message = ZigString.init("Non-regular files aren't supported yet"), - .syscall = ZigString.init("fstat"), - }; - - // blocking, but off the main thread - pub const CopyFile = struct { - destination_file_store: FileStore, - source_file_store: FileStore, - store: ?*Store = null, - source_store: ?*Store = null, - offset: SizeType = 0, - size: SizeType = 0, - max_length: SizeType = Blob.max_size, - destination_fd: JSC.Node.FileDescriptor = null_fd, - source_fd: JSC.Node.FileDescriptor = null_fd, - - system_error: ?SystemError = null, - - read_len: SizeType = 0, - read_off: SizeType = 0, - - globalThis: *JSGlobalObject, - - pub const ResultType = anyerror!SizeType; - - pub const Callback = fn (ctx: *anyopaque, len: ResultType) void; - pub const CopyFilePromiseTask = JSC.ConcurrentPromiseTask(CopyFile); - pub const CopyFilePromiseTaskEventLoopTask = CopyFilePromiseTask.EventLoopTask; - - pub fn create( - allocator: std.mem.Allocator, - store: *Store, - source_store: *Store, - off: SizeType, - max_len: SizeType, - globalThis: *JSC.JSGlobalObject, - ) !*CopyFilePromiseTask { - var read_file = try allocator.create(CopyFile); - read_file.* = CopyFile{ - .store = store, - .source_store = source_store, - .offset = off, - .max_length = max_len, - .globalThis = globalThis, - .destination_file_store = store.data.file, - .source_file_store = source_store.data.file, - }; - store.ref(); - source_store.ref(); - return try CopyFilePromiseTask.createOnJSThread(allocator, globalThis, read_file); - } - - const linux = std.os.linux; - const darwin = std.os.darwin; - - pub fn deinit(this: *CopyFile) void { - if (this.source_file_store.pathlike == .path) { - if (this.source_file_store.pathlike.path == .string and this.system_error == null) { - bun.default_allocator.free(bun.constStrToU8(this.source_file_store.pathlike.path.slice())); - } - } - this.store.?.deref(); - - bun.default_allocator.destroy(this); - } - - pub fn reject(this: *CopyFile, promise: *JSC.JSInternalPromise) void { - var globalThis = this.globalThis; - var system_error: SystemError = this.system_error orelse SystemError{}; - if (this.source_file_store.pathlike == .path and system_error.path.len == 0) { - system_error.path = ZigString.init(this.source_file_store.pathlike.path.slice()); - system_error.path.mark(); - } - - if (system_error.message.len == 0) { - system_error.message = ZigString.init("Failed to copy file"); - } - - var instance = system_error.toErrorInstance(this.globalThis); - if (this.store) |store| { - store.deref(); - } - promise.reject(globalThis, instance); - } - - pub fn then(this: *CopyFile, promise: *JSC.JSInternalPromise) void { - this.source_store.?.deref(); - - if (this.system_error != null) { - this.reject(promise); - return; - } - - promise.resolve(this.globalThis, JSC.JSValue.jsNumberFromUint64(this.read_len)); - } - - pub fn run(this: *CopyFile) void { - this.runAsync(); - } - - pub fn doClose(this: *CopyFile) void { - const close_input = this.destination_file_store.pathlike != .fd and this.destination_fd != null_fd; - const close_output = this.source_file_store.pathlike != .fd and this.source_fd != null_fd; - - if (close_input and close_output) { - this.doCloseFile(.both); - } else if (close_input) { - this.doCloseFile(.destination); - } else if (close_output) { - this.doCloseFile(.source); - } - } - - const os = std.os; - - pub fn doCloseFile(this: *CopyFile, comptime which: IOWhich) void { - switch (which) { - .both => { - _ = JSC.Node.Syscall.close(this.destination_fd); - _ = JSC.Node.Syscall.close(this.source_fd); - }, - .destination => { - _ = JSC.Node.Syscall.close(this.destination_fd); - }, - .source => { - _ = JSC.Node.Syscall.close(this.source_fd); - }, - } - } - - const O = if (Environment.isLinux) linux.O else std.os.O; - const open_destination_flags = O.CLOEXEC | O.CREAT | O.WRONLY | O.TRUNC; - const open_source_flags = O.CLOEXEC | O.RDONLY; - - pub fn doOpenFile(this: *CopyFile, comptime which: IOWhich) !void { - // open source file first - // if it fails, we don't want the extra destination file hanging out - if (which == .both or which == .source) { - this.source_fd = switch (JSC.Node.Syscall.open( - this.source_file_store.pathlike.path.sliceZAssume(), - open_source_flags, - 0, - )) { - .result => |result| result, - .err => |errno| { - this.system_error = errno.toSystemError(); - return AsyncIO.asError(errno.errno); - }, - }; - } - - if (which == .both or which == .destination) { - this.destination_fd = switch (JSC.Node.Syscall.open( - this.destination_file_store.pathlike.path.sliceZAssume(), - open_destination_flags, - JSC.Node.default_permission, - )) { - .result => |result| result, - .err => |errno| { - if (which == .both) { - _ = JSC.Node.Syscall.close(this.source_fd); - this.source_fd = 0; - } - - this.system_error = errno.toSystemError(); - return AsyncIO.asError(errno.errno); - }, - }; - } - } - - const TryWith = enum { - sendfile, - copy_file_range, - splice, - - pub const tag = std.EnumMap(TryWith, JSC.Node.Syscall.Tag).init(.{ - .sendfile = .sendfile, - .copy_file_range = .copy_file_range, - .splice = .splice, - }); - }; - - pub fn doCopyFileRange( - this: *CopyFile, - comptime use: TryWith, - comptime clear_append_if_invalid: bool, - ) anyerror!void { - this.read_off += this.offset; - - var remain = @as(usize, this.max_length); - if (remain == max_size or remain == 0) { - // sometimes stat lies - // let's give it 4096 and see how it goes - remain = 4096; - } - - var total_written: usize = 0; - const src_fd = this.source_fd; - const dest_fd = this.destination_fd; - - defer { - this.read_len = @truncate(SizeType, total_written); - } - - var has_unset_append = false; - - while (true) { - const written = switch (comptime use) { - .copy_file_range => linux.copy_file_range(src_fd, null, dest_fd, null, remain, 0), - .sendfile => linux.sendfile(dest_fd, src_fd, null, remain), - .splice => bun.C.splice(src_fd, null, dest_fd, null, remain, 0), - }; - - switch (linux.getErrno(written)) { - .SUCCESS => {}, - - .INVAL => { - if (comptime clear_append_if_invalid) { - if (!has_unset_append) { - // https://kylelaker.com/2018/08/31/stdout-oappend.html - // make() can set STDOUT / STDERR to O_APPEND - // this messes up sendfile() - has_unset_append = true; - const flags = linux.fcntl(dest_fd, linux.F.GETFL, 0); - if ((flags & O.APPEND) != 0) { - _ = linux.fcntl(dest_fd, linux.F.SETFL, flags ^ O.APPEND); - continue; - } - } - } - - this.system_error = (JSC.Node.Syscall.Error{ - .errno = @intCast(JSC.Node.Syscall.Error.Int, @enumToInt(linux.E.INVAL)), - .syscall = TryWith.tag.get(use).?, - }).toSystemError(); - return AsyncIO.asError(linux.E.INVAL); - }, - else => |errno| { - this.system_error = (JSC.Node.Syscall.Error{ - .errno = @intCast(JSC.Node.Syscall.Error.Int, @enumToInt(errno)), - .syscall = TryWith.tag.get(use).?, - }).toSystemError(); - return AsyncIO.asError(errno); - }, - } - - // wrote zero bytes means EOF - remain -|= written; - total_written += written; - if (written == 0 or remain == 0) break; - } - } - - pub fn doFCopyFile(this: *CopyFile) anyerror!void { - switch (JSC.Node.Syscall.fcopyfile(this.source_fd, this.destination_fd, os.system.COPYFILE_DATA)) { - .err => |errno| { - this.system_error = errno.toSystemError(); - - return AsyncIO.asError(errno.errno); - }, - .result => {}, - } - } - - pub fn doClonefile(this: *CopyFile) anyerror!void { - var source_buf: [bun.MAX_PATH_BYTES]u8 = undefined; - var dest_buf: [bun.MAX_PATH_BYTES]u8 = undefined; - - switch (JSC.Node.Syscall.clonefile( - this.source_file_store.pathlike.path.sliceZ(&source_buf), - this.destination_file_store.pathlike.path.sliceZ( - &dest_buf, - ), - )) { - .err => |errno| { - this.system_error = errno.toSystemError(); - return AsyncIO.asError(errno.errno); - }, - .result => {}, - } - } - - pub fn runAsync(this: *CopyFile) void { - // defer task.onFinish(); - - var stat_: ?std.os.Stat = null; - - if (this.destination_file_store.pathlike == .fd) { - this.destination_fd = this.destination_file_store.pathlike.fd; - } - - if (this.source_file_store.pathlike == .fd) { - this.source_fd = this.source_file_store.pathlike.fd; - } - - // Do we need to open both files? - if (this.destination_fd == null_fd and this.source_fd == null_fd) { - - // First, we attempt to clonefile() on macOS - // This is the fastest way to copy a file. - if (comptime Environment.isMac) { - if (this.offset == 0 and this.source_file_store.pathlike == .path and this.destination_file_store.pathlike == .path) { - do_clonefile: { - - // stat the output file, make sure it: - // 1. Exists - switch (JSC.Node.Syscall.stat(this.source_file_store.pathlike.path.sliceZAssume())) { - .result => |result| { - stat_ = result; - - if (os.S.ISDIR(result.mode)) { - this.system_error = unsupported_directory_error; - return; - } - - if (!os.S.ISREG(result.mode)) - break :do_clonefile; - }, - .err => |err| { - // If we can't stat it, we also can't copy it. - this.system_error = err.toSystemError(); - return; - }, - } - - if (this.doClonefile()) { - if (this.max_length != Blob.max_size and this.max_length < @intCast(SizeType, stat_.?.size)) { - // If this fails...well, there's not much we can do about it. - _ = bun.C.truncate( - this.destination_file_store.pathlike.path.sliceZAssume(), - @intCast(std.os.off_t, this.max_length), - ); - this.read_len = @intCast(SizeType, this.max_length); - } else { - this.read_len = @intCast(SizeType, stat_.?.size); - } - return; - } else |_| { - - // this may still fail, in which case we just continue trying with fcopyfile - // it can fail when the input file already exists - // or if the output is not a directory - // or if it's a network volume - this.system_error = null; - } - } - } - } - - this.doOpenFile(.both) catch return; - // Do we need to open only one file? - } else if (this.destination_fd == null_fd) { - this.source_fd = this.source_file_store.pathlike.fd; - - this.doOpenFile(.destination) catch return; - // Do we need to open only one file? - } else if (this.source_fd == null_fd) { - this.destination_fd = this.destination_file_store.pathlike.fd; - - this.doOpenFile(.source) catch return; - } - - if (this.system_error != null) { - return; - } - - std.debug.assert(this.destination_fd != null_fd); - std.debug.assert(this.source_fd != null_fd); - - if (this.destination_file_store.pathlike == .fd) {} - - const stat: std.os.Stat = stat_ orelse switch (JSC.Node.Syscall.fstat(this.source_fd)) { - .result => |result| result, - .err => |err| { - this.doClose(); - this.system_error = err.toSystemError(); - return; - }, - }; - - if (os.S.ISDIR(stat.mode)) { - this.system_error = unsupported_directory_error; - this.doClose(); - return; - } - - if (stat.size != 0) { - this.max_length = @maximum(@minimum(@intCast(SizeType, stat.size), this.max_length), this.offset) - this.offset; - if (this.max_length == 0) { - this.doClose(); - return; - } - - if (os.S.ISREG(stat.mode) and - this.max_length > std.mem.page_size and - this.max_length != Blob.max_size) - { - bun.C.preallocate_file(this.destination_fd, 0, this.max_length) catch {}; - } - } - - if (comptime Environment.isLinux) { - - // Bun.write(Bun.file("a"), Bun.file("b")) - if (os.S.ISREG(stat.mode) and (os.S.ISREG(this.destination_file_store.mode) or this.destination_file_store.mode == 0)) { - if (this.destination_file_store.is_atty orelse false) { - this.doCopyFileRange(.copy_file_range, true) catch {}; - } else { - this.doCopyFileRange(.copy_file_range, false) catch {}; - } - - this.doClose(); - return; - } - - // $ bun run foo.js | bun run bar.js - if (os.S.ISFIFO(stat.mode) and os.S.ISFIFO(this.destination_file_store.mode)) { - if (this.destination_file_store.is_atty orelse false) { - this.doCopyFileRange(.splice, true) catch {}; - } else { - this.doCopyFileRange(.splice, false) catch {}; - } - - this.doClose(); - return; - } - - if (os.S.ISREG(stat.mode) or os.S.ISCHR(stat.mode) or os.S.ISSOCK(stat.mode)) { - if (this.destination_file_store.is_atty orelse false) { - this.doCopyFileRange(.sendfile, true) catch {}; - } else { - this.doCopyFileRange(.sendfile, false) catch {}; - } - - this.doClose(); - return; - } - - this.system_error = unsupported_non_regular_file_error; - this.doClose(); - return; - } - - if (comptime Environment.isMac) { - this.doFCopyFile() catch { - this.doClose(); - - return; - }; - if (stat.size != 0 and @intCast(SizeType, stat.size) > this.max_length) { - _ = darwin.ftruncate(this.destination_fd, @intCast(std.os.off_t, this.max_length)); - } - - this.doClose(); - } else { - @compileError("TODO: implement copyfile"); - } - } - }; - }; - - pub const FileStore = struct { - pathlike: JSC.Node.PathOrFileDescriptor, - mime_type: HTTPClient.MimeType = HTTPClient.MimeType.other, - is_atty: ?bool = null, - mode: JSC.Node.Mode = 0, - seekable: ?bool = null, - max_size: SizeType = 0, - - pub fn init(pathlike: JSC.Node.PathOrFileDescriptor, mime_type: ?HTTPClient.MimeType) FileStore { - return .{ .pathlike = pathlike, .mime_type = mime_type orelse HTTPClient.MimeType.other }; - } - }; - - pub const ByteStore = struct { - ptr: [*]u8 = undefined, - len: SizeType = 0, - cap: SizeType = 0, - allocator: std.mem.Allocator, - - pub fn init(bytes: []u8, allocator: std.mem.Allocator) ByteStore { - return .{ - .ptr = bytes.ptr, - .len = @truncate(SizeType, bytes.len), - .cap = @truncate(SizeType, bytes.len), - .allocator = allocator, - }; - } - - pub fn fromArrayList(list: std.ArrayListUnmanaged(u8), allocator: std.mem.Allocator) !*ByteStore { - return ByteStore.init(list.items, allocator); - } - - pub fn slice(this: ByteStore) []u8 { - return this.ptr[0..this.len]; - } - - pub fn deinit(this: *ByteStore) void { - this.allocator.free(this.ptr[0..this.cap]); - } - - pub fn asArrayList(this: ByteStore) std.ArrayListUnmanaged(u8) { - return this.asArrayListLeak(); - } - - pub fn asArrayListLeak(this: ByteStore) std.ArrayListUnmanaged(u8) { - return .{ - .items = this.ptr[0..this.len], - .capacity = this.cap, - }; - } - }; - - pub const Constructor = JSC.NewConstructor( - Blob, - .{ - .constructor = .{ .rfn = constructor }, - }, - .{}, - ); - - pub const Class = NewClass( - Blob, - .{ .name = "Blob" }, - .{ .finalize = finalize, .text = .{ - .rfn = getText, - }, .json = .{ - .rfn = getJSON, - }, .arrayBuffer = .{ - .rfn = getArrayBuffer, - }, .slice = .{ - .rfn = getSlice, - }, .stream = .{ - .rfn = getStream, - } }, - .{ - .@"type" = .{ - .get = getType, - .set = setType, - }, - .@"size" = .{ - .get = getSize, - .ro = true, - }, - }, - ); - - pub fn getStream( - this: *Blob, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - arguments: []const js.JSValueRef, - exception: js.ExceptionRef, - ) JSC.C.JSValueRef { - var recommended_chunk_size: SizeType = 0; - if (arguments.len > 0) { - if (!JSValue.c(arguments[0]).isNumber() and !JSValue.c(arguments[0]).isUndefinedOrNull()) { - JSC.throwInvalidArguments("chunkSize must be a number", .{}, ctx, exception); - return null; - } - - recommended_chunk_size = @intCast(SizeType, @maximum(0, @truncate(i52, JSValue.c(arguments[0]).toInt64()))); - } - return JSC.WebCore.ReadableStream.fromBlob( - ctx.ptr(), - this, - recommended_chunk_size, - ).asObjectRef(); - } - - fn promisified( - value: JSC.JSValue, - global: *JSGlobalObject, - ) JSC.JSValue { - if (value.isError()) { - return JSC.JSPromise.rejectedPromiseValue(global, value); - } - - if (value.jsType() == .JSPromise) - return value; - - return JSC.JSPromise.resolvedPromiseValue(global, value); - } - - pub fn getText( - this: *Blob, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - _: []const js.JSValueRef, - _: js.ExceptionRef, - ) JSC.C.JSObjectRef { - return promisified(this.toString(ctx.ptr(), .clone), ctx.ptr()).asObjectRef(); - } - - pub fn getTextTransfer( - this: *Blob, - ctx: js.JSContextRef, - ) JSC.C.JSObjectRef { - return promisified(this.toString(ctx.ptr(), .transfer), ctx.ptr()).asObjectRef(); - } - - pub fn getJSON( - this: *Blob, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - _: []const js.JSValueRef, - _: js.ExceptionRef, - ) JSC.C.JSObjectRef { - return promisified(this.toJSON(ctx.ptr(), .share), ctx.ptr()).asObjectRef(); - } - - pub fn getArrayBufferTransfer( - this: *Blob, - ctx: js.JSContextRef, - ) JSC.C.JSObjectRef { - return promisified(this.toArrayBuffer(ctx.ptr(), .transfer), ctx.ptr()).asObjectRef(); - } - - pub fn getArrayBuffer( - this: *Blob, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - _: []const js.JSValueRef, - _: js.ExceptionRef, - ) JSC.C.JSObjectRef { - return promisified(this.toArrayBuffer(ctx.ptr(), .clone), ctx.ptr()).asObjectRef(); - } - - /// https://w3c.github.io/FileAPI/#slice-method-algo - /// The slice() method returns a new Blob object with bytes ranging from the - /// optional start parameter up to but not including the optional end - /// parameter, and with a type attribute that is the value of the optional - /// contentType parameter. It must act as follows: - pub fn getSlice( - this: *Blob, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - args: []const js.JSValueRef, - exception: js.ExceptionRef, - ) JSC.C.JSObjectRef { - if (this.size == 0) { - return constructor(ctx, null, &[_]js.JSValueRef{}, exception); - } - // If the optional start parameter is not used as a parameter when making this call, let relativeStart be 0. - var relativeStart: i64 = 0; - - // If the optional end parameter is not used as a parameter when making this call, let relativeEnd be size. - var relativeEnd: i64 = @intCast(i64, this.size); - - var args_iter = JSC.Node.ArgumentsSlice.from(ctx.bunVM(), args); - if (args_iter.nextEat()) |start_| { - const start = start_.toInt64(); - if (start < 0) { - // If the optional start parameter is negative, let relativeStart be start + size. - relativeStart = @intCast(i64, @maximum(start + @intCast(i64, this.size), 0)); - } else { - // Otherwise, let relativeStart be start. - relativeStart = @minimum(@intCast(i64, start), @intCast(i64, this.size)); - } - } - - if (args_iter.nextEat()) |end_| { - const end = end_.toInt64(); - // If end is negative, let relativeEnd be max((size + end), 0). - if (end < 0) { - // If the optional start parameter is negative, let relativeStart be start + size. - relativeEnd = @intCast(i64, @maximum(end + @intCast(i64, this.size), 0)); - } else { - // Otherwise, let relativeStart be start. - relativeEnd = @minimum(@intCast(i64, end), @intCast(i64, this.size)); - } - } - - var content_type: string = ""; - if (args_iter.nextEat()) |content_type_| { - if (content_type_.isString()) { - var zig_str = content_type_.getZigString(ctx.ptr()); - var slicer = zig_str.toSlice(bun.default_allocator); - defer slicer.deinit(); - var slice = slicer.slice(); - var content_type_buf = getAllocator(ctx).alloc(u8, slice.len) catch unreachable; - content_type = strings.copyLowercase(slice, content_type_buf); - } - } - - const len = @intCast(SizeType, @maximum(relativeEnd - relativeStart, 0)); - - // This copies over the is_all_ascii flag - // which is okay because this will only be a <= slice - var blob = this.dupe(); - blob.offset = @intCast(SizeType, relativeStart); - blob.size = len; - blob.content_type = content_type; - blob.content_type_allocated = content_type.len > 0; - - var blob_ = getAllocator(ctx).create(Blob) catch unreachable; - blob_.* = blob; - blob_.allocator = getAllocator(ctx); - return Blob.Class.make(ctx, blob_); - } - - pub fn getType( - this: *Blob, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSStringRef, - _: js.ExceptionRef, - ) js.JSValueRef { - return ZigString.init(this.content_type).toValue(ctx.ptr()).asObjectRef(); - } - - pub fn setType( - this: *Blob, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSStringRef, - value: js.JSValueRef, - _: js.ExceptionRef, - ) bool { - var zig_str = JSValue.fromRef(value).getZigString(ctx.ptr()); - if (zig_str.is16Bit()) - return false; - - var slice = zig_str.trimmedSlice(); - if (strings.eql(slice, this.content_type)) - return true; - - const prev_content_type = this.content_type; - { - defer if (this.content_type_allocated) bun.default_allocator.free(prev_content_type); - var content_type_buf = getAllocator(ctx).alloc(u8, slice.len) catch unreachable; - this.content_type = strings.copyLowercase(slice, content_type_buf); - } - - this.content_type_allocated = true; - return true; - } - - pub fn getSize( - this: *Blob, - _: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSStringRef, - _: js.ExceptionRef, - ) js.JSValueRef { - if (this.size == Blob.max_size) { - this.resolveSize(); - if (this.size == Blob.max_size and this.store != null) { - return JSValue.jsNumberFromChar(0).asRef(); - } - } - - if (this.size < std.math.maxInt(i32)) { - return JSValue.jsNumber(this.size).asRef(); - } - - return JSC.JSValue.jsNumberFromUint64(this.size).asRef(); - } - - pub fn resolveSize(this: *Blob) void { - if (this.store) |store| { - if (store.data == .bytes) { - const offset = this.offset; - const store_size = store.size(); - if (store_size != Blob.max_size) { - this.offset = @minimum(store_size, offset); - this.size = store_size - offset; - } - } - } else { - this.size = 0; - } - } - - pub fn constructor( - ctx: js.JSContextRef, - _: js.JSObjectRef, - args: []const js.JSValueRef, - exception: js.ExceptionRef, - ) js.JSObjectRef { - var blob: Blob = undefined; - switch (args.len) { - 0 => { - var empty: []u8 = &[_]u8{}; - blob = Blob.init(empty, getAllocator(ctx), ctx.ptr()); - }, - else => { - blob = fromJS(ctx.ptr(), JSValue.fromRef(args[0]), false, true) catch |err| { - if (err == error.InvalidArguments) { - JSC.JSError(getAllocator(ctx), "new Blob() expects an Array", .{}, ctx, exception); - return null; - } - JSC.JSError(getAllocator(ctx), "out of memory :(", .{}, ctx, exception); - return null; - }; - - if (args.len > 1) { - var options = JSValue.fromRef(args[1]); - if (options.isCell()) { - // type, the ASCII-encoded string in lower case - // representing the media type of the Blob. - // Normative conditions for this member are provided - // in the § 3.1 Constructors. - if (options.get(ctx.ptr(), "type")) |content_type| { - if (content_type.isString()) { - var content_type_str = content_type.getZigString(ctx.ptr()); - if (!content_type_str.is16Bit()) { - var slice = content_type_str.trimmedSlice(); - var content_type_buf = getAllocator(ctx).alloc(u8, slice.len) catch unreachable; - blob.content_type = strings.copyLowercase(slice, content_type_buf); - blob.content_type_allocated = true; - } - } - } - } - } - - if (blob.content_type.len == 0) { - blob.content_type = ""; - } - }, - } - - var blob_ = getAllocator(ctx).create(Blob) catch unreachable; - blob_.* = blob; - blob_.allocator = getAllocator(ctx); - return Blob.Class.make(ctx, blob_); - } - - pub fn finalize(this: *Blob) void { - this.deinit(); - } - - pub fn initWithAllASCII(bytes: []u8, allocator: std.mem.Allocator, globalThis: *JSGlobalObject, is_all_ascii: bool) Blob { - // avoid allocating a Blob.Store if the buffer is actually empty - var store: ?*Blob.Store = null; - if (bytes.len > 0) { - store = Blob.Store.init(bytes, allocator) catch unreachable; - store.?.is_all_ascii = is_all_ascii; - } - return Blob{ - .size = @truncate(SizeType, bytes.len), - .store = store, - .allocator = null, - .content_type = "", - .globalThis = globalThis, - .is_all_ascii = is_all_ascii, - }; - } - - pub fn init(bytes: []u8, allocator: std.mem.Allocator, globalThis: *JSGlobalObject) Blob { - return Blob{ - .size = @truncate(SizeType, bytes.len), - .store = if (bytes.len > 0) - Blob.Store.init(bytes, allocator) catch unreachable - else - null, - .allocator = null, - .content_type = "", - .globalThis = globalThis, - }; - } - - pub fn initWithStore(store: *Blob.Store, globalThis: *JSGlobalObject) Blob { - return Blob{ - .size = store.size(), - .store = store, - .allocator = null, - .content_type = if (store.data == .file) - store.data.file.mime_type.value - else - "", - .globalThis = globalThis, - }; - } - - pub fn initEmpty(globalThis: *JSGlobalObject) Blob { - return Blob{ - .size = 0, - .store = null, - .allocator = null, - .content_type = "", - .globalThis = globalThis, - }; - } - - // Transferring doesn't change the reference count - // It is a move - inline fn transfer(this: *Blob) void { - this.store = null; - } - - pub fn detach(this: *Blob) void { - if (this.store != null) this.store.?.deref(); - this.store = null; - } - - /// This does not duplicate - /// This creates a new view - /// and increment the reference count - pub fn dupe(this: *const Blob) Blob { - if (this.store != null) this.store.?.ref(); - var duped = this.*; - duped.allocator = null; - return duped; - } - - pub fn deinit(this: *Blob) void { - this.detach(); - - if (this.allocator) |alloc| { - this.allocator = null; - alloc.destroy(this); - } - } - - pub fn sharedView(this: *const Blob) []const u8 { - if (this.size == 0 or this.store == null) return ""; - var slice_ = this.store.?.sharedView(); - if (slice_.len == 0) return ""; - slice_ = slice_[this.offset..]; - - return slice_[0..@minimum(slice_.len, @as(usize, this.size))]; - } - - pub fn view(this: *const Blob) []const u8 { - if (this.size == 0 or this.store == null) return ""; - return this.store.?.sharedView()[this.offset..][0..this.size]; - } - - pub const Lifetime = JSC.WebCore.Lifetime; - pub fn setIsASCIIFlag(this: *Blob, is_all_ascii: bool) void { - this.is_all_ascii = is_all_ascii; - // if this Blob represents the entire binary data - // which will be pretty common - // we can update the store's is_all_ascii flag - // and any other Blob that points to the same store - // can skip checking the encoding - if (this.size > 0 and this.offset == 0 and this.store.?.data == .bytes) { - this.store.?.is_all_ascii = is_all_ascii; - } - } - - pub fn NewReadFileHandler(comptime Function: anytype, comptime lifetime: Lifetime) type { - return struct { - context: Blob, - promise: *JSPromise, - globalThis: *JSGlobalObject, - pub fn run(handler: *@This(), bytes_: Blob.Store.ReadFile.ResultType) void { - var promise = handler.promise; - var blob = handler.context; - blob.allocator = null; - var globalThis = handler.globalThis; - bun.default_allocator.destroy(handler); - switch (bytes_) { - .result => |result| { - const bytes = result.buf; - const is_temporary = result.is_temporary; - if (blob.size > 0) - blob.size = @minimum(@truncate(u32, bytes.len), blob.size); - if (!is_temporary) { - promise.resolve(globalThis, Function(&blob, globalThis, bytes, comptime lifetime)); - } else { - promise.resolve(globalThis, Function(&blob, globalThis, bytes, .temporary)); - } - }, - .err => |err| { - promise.reject(globalThis, err.toErrorInstance(globalThis)); - }, - } - } - }; - } - - pub const WriteFilePromise = struct { - promise: *JSPromise, - globalThis: *JSGlobalObject, - pub fn run(handler: *@This(), count: Blob.Store.WriteFile.ResultType) void { - var promise = handler.promise; - var globalThis = handler.globalThis; - bun.default_allocator.destroy(handler); - switch (count) { - .err => |err| { - promise.reject(globalThis, err.toErrorInstance(globalThis)); - }, - .result => |wrote| { - promise.resolve(globalThis, JSC.JSValue.jsNumberFromUint64(wrote)); - }, - } - } - }; - - pub fn NewInternalReadFileHandler(comptime Context: type, comptime Function: anytype) type { - return struct { - pub fn run(handler: *anyopaque, bytes_: Store.ReadFile.ResultType) void { - Function(bun.cast(Context, handler), bytes_); - } - }; - } - - pub fn doReadFileInternal(this: *Blob, comptime Handler: type, ctx: Handler, comptime Function: anytype, global: *JSGlobalObject) void { - var file_read = Store.ReadFile.createWithCtx( - bun.default_allocator, - this.store.?, - ctx, - NewInternalReadFileHandler(Handler, Function).run, - this.offset, - this.size, - ) catch unreachable; - var read_file_task = Store.ReadFile.ReadFileTask.createOnJSThread(bun.default_allocator, global, file_read) catch unreachable; - read_file_task.schedule(); - } - - pub fn doReadFile(this: *Blob, comptime Function: anytype, comptime lifetime: Lifetime, global: *JSGlobalObject) JSValue { - const Handler = NewReadFileHandler(Function, lifetime); - var promise = JSPromise.create(global); - - var handler = Handler{ - .context = this.*, - .promise = promise, - .globalThis = global, - }; - - var ptr = bun.default_allocator.create(Handler) catch unreachable; - ptr.* = handler; - var file_read = Store.ReadFile.create( - bun.default_allocator, - this.store.?, - this.offset, - this.size, - *Handler, - ptr, - Handler.run, - ) catch unreachable; - var read_file_task = Store.ReadFile.ReadFileTask.createOnJSThread(bun.default_allocator, global, file_read) catch unreachable; - read_file_task.schedule(); - return promise.asValue(global); - } - - pub fn needsToReadFile(this: *const Blob) bool { - return this.store != null and this.store.?.data == .file; - } - - pub fn toStringWithBytes(this: *Blob, global: *JSGlobalObject, buf: []const u8, comptime lifetime: Lifetime) JSValue { - // null == unknown - // false == can't be - const could_be_all_ascii = this.is_all_ascii orelse this.store.?.is_all_ascii; - - if (could_be_all_ascii == null or !could_be_all_ascii.?) { - // if toUTF16Alloc returns null, it means there are no non-ASCII characters - // instead of erroring, invalid characters will become a U+FFFD replacement character - if (strings.toUTF16Alloc(bun.default_allocator, buf, false) catch unreachable) |external| { - if (lifetime != .temporary) - this.setIsASCIIFlag(false); - - if (lifetime == .transfer) { - this.detach(); - } - - if (lifetime == .temporary) { - bun.default_allocator.free(bun.constStrToU8(buf)); - } - - return ZigString.toExternalU16(external.ptr, external.len, global); - } - - if (lifetime != .temporary) this.setIsASCIIFlag(true); - } - - switch (comptime lifetime) { - // strings are immutable - // we don't need to clone - .clone => { - this.store.?.ref(); - return ZigString.init(buf).external(global, this.store.?, Store.external); - }, - .transfer => { - var store = this.store.?; - this.transfer(); - return ZigString.init(buf).external(global, store, Store.external); - }, - // strings are immutable - // sharing isn't really a thing - .share => { - this.store.?.ref(); - return ZigString.init(buf).external(global, this.store.?, Store.external); - }, - .temporary => { - return ZigString.init(buf).toExternalValue(global); - }, - } - } - - pub fn toString(this: *Blob, global: *JSGlobalObject, comptime lifetime: Lifetime) JSValue { - if (this.needsToReadFile()) { - return this.doReadFile(toStringWithBytes, lifetime, global); - } - - const view_: []u8 = - bun.constStrToU8(this.sharedView()); - - if (view_.len == 0) - return ZigString.Empty.toValue(global); - - return toStringWithBytes(this, global, view_, lifetime); - } - - pub fn toJSON(this: *Blob, global: *JSGlobalObject, comptime lifetime: Lifetime) JSValue { - if (this.needsToReadFile()) { - return this.doReadFile(toJSONWithBytes, lifetime, global); - } - - var view_ = this.sharedView(); - - if (view_.len == 0) - return ZigString.Empty.toValue(global); - - return toJSONWithBytes(this, global, view_, lifetime); - } - - pub fn toJSONWithBytes(this: *Blob, global: *JSGlobalObject, buf: []const u8, comptime lifetime: Lifetime) JSValue { - // null == unknown - // false == can't be - const could_be_all_ascii = this.is_all_ascii orelse this.store.?.is_all_ascii; - - if (could_be_all_ascii == null or !could_be_all_ascii.?) { - // if toUTF16Alloc returns null, it means there are no non-ASCII characters - if (strings.toUTF16Alloc(bun.default_allocator, buf, false) catch null) |external| { - if (comptime lifetime != .temporary) this.setIsASCIIFlag(false); - return ZigString.toExternalU16(external.ptr, external.len, global).parseJSON(global); - } - - if (comptime lifetime != .temporary) this.setIsASCIIFlag(true); - } - - if (comptime lifetime == .temporary) { - return ZigString.init(buf).toExternalValue( - global, - ).parseJSON(global); - } else { - return ZigString.init(buf).toValue( - global, - ).parseJSON(global); - } - } - - pub fn toArrayBufferWithBytes(this: *Blob, global: *JSGlobalObject, buf: []u8, comptime lifetime: Lifetime) JSValue { - switch (comptime lifetime) { - .clone => { - var clone = bun.default_allocator.alloc(u8, buf.len) catch unreachable; - @memcpy(clone.ptr, buf.ptr, buf.len); - - return JSC.ArrayBuffer.fromBytes(clone, .ArrayBuffer).toJS(global.ref(), null); - }, - .share => { - this.store.?.ref(); - return JSC.ArrayBuffer.fromBytes(buf, .ArrayBuffer).toJSWithContext( - global.ref(), - this.store.?, - JSC.BlobArrayBuffer_deallocator, - null, - ); - }, - .transfer => { - var store = this.store.?; - this.transfer(); - return JSC.ArrayBuffer.fromBytes(buf, .ArrayBuffer).toJSWithContext( - global.ref(), - store, - JSC.BlobArrayBuffer_deallocator, - null, - ); - }, - .temporary => { - return JSC.ArrayBuffer.fromBytes(buf, .ArrayBuffer).toJS( - global.ref(), - null, - ); - }, - } - } - - pub fn toArrayBuffer(this: *Blob, global: *JSGlobalObject, comptime lifetime: Lifetime) JSValue { - if (this.needsToReadFile()) { - return this.doReadFile(toArrayBufferWithBytes, lifetime, global); - } - - var view_ = this.sharedView(); - - if (view_.len == 0) - return JSC.ArrayBuffer.fromBytes(&[_]u8{}, .ArrayBuffer).toJS(global.ref(), null); - - return toArrayBufferWithBytes(this, global, bun.constStrToU8(view_), lifetime); - } - - pub inline fn fromJS( - global: *JSGlobalObject, - arg: JSValue, - comptime move: bool, - comptime require_array: bool, - ) anyerror!Blob { - return fromJSMovable(global, arg, move, require_array); - } - - pub inline fn fromJSMove(global: *JSGlobalObject, arg: JSValue) anyerror!Blob { - return fromJSWithoutDeferGC(global, arg, true, false); - } - - pub inline fn fromJSClone(global: *JSGlobalObject, arg: JSValue) anyerror!Blob { - return fromJSWithoutDeferGC(global, arg, false, true); - } - - pub inline fn fromJSCloneOptionalArray(global: *JSGlobalObject, arg: JSValue) anyerror!Blob { - return fromJSWithoutDeferGC(global, arg, false, false); - } - - fn fromJSMovable( - global: *JSGlobalObject, - arg: JSValue, - comptime move: bool, - comptime require_array: bool, - ) anyerror!Blob { - const FromJSFunction = if (comptime move and !require_array) - fromJSMove - else if (!require_array) - fromJSCloneOptionalArray - else - fromJSClone; - const DeferCtx = struct { - args: std.meta.ArgsTuple(@TypeOf(FromJSFunction)), - ret: anyerror!Blob = undefined, - - pub fn run(ctx: ?*anyopaque) callconv(.C) void { - var that = bun.cast(*@This(), ctx.?); - that.ret = @call(.{}, FromJSFunction, that.args); - } - }; - var ctx = DeferCtx{ - .args = .{ - global, - arg, - }, - .ret = undefined, - }; - global.vm().deferGC(&ctx, DeferCtx.run); - return ctx.ret; - } - - fn fromJSWithoutDeferGC( - global: *JSGlobalObject, - arg: JSValue, - comptime move: bool, - comptime require_array: bool, - ) anyerror!Blob { - var current = arg; - if (current.isUndefinedOrNull()) { - return Blob{ .globalThis = global }; - } - - var top_value = current; - var might_only_be_one_thing = false; - switch (current.jsTypeLoose()) { - .Array, .DerivedArray => { - var top_iter = JSC.JSArrayIterator.init(current, global); - might_only_be_one_thing = top_iter.len == 1; - if (top_iter.len == 0) { - return Blob{ .globalThis = global }; - } - if (might_only_be_one_thing) { - top_value = top_iter.next().?; - } - }, - else => { - might_only_be_one_thing = true; - if (require_array) { - return error.InvalidArguments; - } - }, - } - - if (might_only_be_one_thing or !move) { - - // Fast path: one item, we don't need to join - switch (top_value.jsTypeLoose()) { - .Cell, - .NumberObject, - JSC.JSValue.JSType.String, - JSC.JSValue.JSType.StringObject, - JSC.JSValue.JSType.DerivedStringObject, - => { - var sliced = top_value.toSlice(global, bun.default_allocator); - const is_all_ascii = !sliced.allocated; - if (!sliced.allocated and sliced.len > 0) { - sliced.ptr = @ptrCast([*]const u8, (try bun.default_allocator.dupe(u8, sliced.slice())).ptr); - sliced.allocated = true; - } - - return Blob.initWithAllASCII(bun.constStrToU8(sliced.slice()), bun.default_allocator, global, is_all_ascii); - }, - - JSC.JSValue.JSType.ArrayBuffer, - JSC.JSValue.JSType.Int8Array, - JSC.JSValue.JSType.Uint8Array, - JSC.JSValue.JSType.Uint8ClampedArray, - JSC.JSValue.JSType.Int16Array, - JSC.JSValue.JSType.Uint16Array, - JSC.JSValue.JSType.Int32Array, - JSC.JSValue.JSType.Uint32Array, - JSC.JSValue.JSType.Float32Array, - JSC.JSValue.JSType.Float64Array, - JSC.JSValue.JSType.BigInt64Array, - JSC.JSValue.JSType.BigUint64Array, - JSC.JSValue.JSType.DataView, - => { - var buf = try bun.default_allocator.dupe(u8, top_value.asArrayBuffer(global).?.byteSlice()); - - return Blob.init(buf, bun.default_allocator, global); - }, - - else => { - if (JSC.C.JSObjectGetPrivate(top_value.asObjectRef())) |priv| { - var data = JSC.JSPrivateDataPtr.from(priv); - switch (data.tag()) { - .Blob => { - var blob: *Blob = data.as(Blob); - if (comptime move) { - var _blob = blob.*; - _blob.allocator = null; - blob.transfer(); - return _blob; - } else { - return blob.dupe(); - } - }, - - else => return Blob.initEmpty(global), - } - } - }, - } - } - - var stack_allocator = std.heap.stackFallback(1024, bun.default_allocator); - var stack_mem_all = stack_allocator.get(); - var stack: std.ArrayList(JSValue) = std.ArrayList(JSValue).init(stack_mem_all); - var joiner = StringJoiner{ .use_pool = false, .node_allocator = stack_mem_all }; - var could_have_non_ascii = false; - - defer if (stack_allocator.fixed_buffer_allocator.end_index >= 1024) stack.deinit(); - - while (true) { - switch (current.jsTypeLoose()) { - .NumberObject, - JSC.JSValue.JSType.String, - JSC.JSValue.JSType.StringObject, - JSC.JSValue.JSType.DerivedStringObject, - => { - var sliced = current.toSlice(global, bun.default_allocator); - could_have_non_ascii = could_have_non_ascii or sliced.allocated; - joiner.append( - sliced.slice(), - 0, - if (sliced.allocated) sliced.allocator else null, - ); - }, - - .Array, .DerivedArray => { - var iter = JSC.JSArrayIterator.init(current, global); - try stack.ensureUnusedCapacity(iter.len); - var any_arrays = false; - while (iter.next()) |item| { - if (item.isUndefinedOrNull()) continue; - - // When it's a string or ArrayBuffer inside an array, we can avoid the extra push/pop - // we only really want this for nested arrays - // However, we must preserve the order - // That means if there are any arrays - // we have to restart the loop - if (!any_arrays) { - switch (item.jsTypeLoose()) { - .NumberObject, - .Cell, - JSC.JSValue.JSType.String, - JSC.JSValue.JSType.StringObject, - JSC.JSValue.JSType.DerivedStringObject, - => { - var sliced = item.toSlice(global, bun.default_allocator); - could_have_non_ascii = could_have_non_ascii or sliced.allocated; - joiner.append( - sliced.slice(), - 0, - if (sliced.allocated) sliced.allocator else null, - ); - continue; - }, - JSC.JSValue.JSType.ArrayBuffer, - JSC.JSValue.JSType.Int8Array, - JSC.JSValue.JSType.Uint8Array, - JSC.JSValue.JSType.Uint8ClampedArray, - JSC.JSValue.JSType.Int16Array, - JSC.JSValue.JSType.Uint16Array, - JSC.JSValue.JSType.Int32Array, - JSC.JSValue.JSType.Uint32Array, - JSC.JSValue.JSType.Float32Array, - JSC.JSValue.JSType.Float64Array, - JSC.JSValue.JSType.BigInt64Array, - JSC.JSValue.JSType.BigUint64Array, - JSC.JSValue.JSType.DataView, - => { - could_have_non_ascii = true; - var buf = item.asArrayBuffer(global).?; - joiner.append(buf.byteSlice(), 0, null); - continue; - }, - .Array, .DerivedArray => { - any_arrays = true; - could_have_non_ascii = true; - break; - }, - else => { - if (JSC.C.JSObjectGetPrivate(item.asObjectRef())) |priv| { - var data = JSC.JSPrivateDataPtr.from(priv); - switch (data.tag()) { - .Blob => { - var blob: *Blob = data.as(Blob); - could_have_non_ascii = could_have_non_ascii or !(blob.is_all_ascii orelse false); - joiner.append(blob.sharedView(), 0, null); - continue; - }, - else => {}, - } - } - }, - } - } - - stack.appendAssumeCapacity(item); - } - }, - - JSC.JSValue.JSType.ArrayBuffer, - JSC.JSValue.JSType.Int8Array, - JSC.JSValue.JSType.Uint8Array, - JSC.JSValue.JSType.Uint8ClampedArray, - JSC.JSValue.JSType.Int16Array, - JSC.JSValue.JSType.Uint16Array, - JSC.JSValue.JSType.Int32Array, - JSC.JSValue.JSType.Uint32Array, - JSC.JSValue.JSType.Float32Array, - JSC.JSValue.JSType.Float64Array, - JSC.JSValue.JSType.BigInt64Array, - JSC.JSValue.JSType.BigUint64Array, - JSC.JSValue.JSType.DataView, - => { - var buf = current.asArrayBuffer(global).?; - joiner.append(buf.slice(), 0, null); - could_have_non_ascii = true; - }, - - else => { - outer: { - if (JSC.C.JSObjectGetPrivate(current.asObjectRef())) |priv| { - var data = JSC.JSPrivateDataPtr.from(priv); - switch (data.tag()) { - .Blob => { - var blob: *Blob = data.as(Blob); - could_have_non_ascii = could_have_non_ascii or !(blob.is_all_ascii orelse false); - joiner.append(blob.sharedView(), 0, null); - break :outer; - }, - else => {}, - } - } - - var sliced = current.toSlice(global, bun.default_allocator); - could_have_non_ascii = could_have_non_ascii or sliced.allocated; - joiner.append( - sliced.slice(), - 0, - if (sliced.allocated) sliced.allocator else null, - ); - } - }, - } - current = stack.popOrNull() orelse break; - } - - var joined = try joiner.done(bun.default_allocator); - - if (!could_have_non_ascii) { - return Blob.initWithAllASCII(joined, bun.default_allocator, global, true); - } - return Blob.init(joined, bun.default_allocator, global); - } -}; - -// https://developer.mozilla.org/en-US/docs/Web/API/Body -pub const Body = struct { - init: Init = Init{ .headers = null, .status_code = 200 }, - value: Value = Value.empty, - - pub inline fn len(this: *const Body) usize { - return this.slice().len; - } - - pub fn slice(this: *const Body) []const u8 { - return this.value.slice(); - } - - pub fn use(this: *Body) Blob { - return this.value.use(); - } - - pub fn clone(this: Body, allocator: std.mem.Allocator, globalThis: *JSGlobalObject) Body { - return Body{ - .init = this.init.clone(globalThis), - .value = this.value.clone(allocator), - }; - } - - pub fn writeFormat(this: *const Body, formatter: *JSC.Formatter, writer: anytype, comptime enable_ansi_colors: bool) !void { - const Writer = @TypeOf(writer); - - try formatter.writeIndent(Writer, writer); - try writer.writeAll("bodyUsed: "); - formatter.printAs(.Boolean, Writer, writer, JSC.JSValue.jsBoolean(this.value == .Used), .BooleanObject, enable_ansi_colors); - try formatter.printComma(Writer, writer, enable_ansi_colors); - try writer.writeAll("\n"); - - // if (this.init.headers) |headers| { - // try formatter.writeIndent(Writer, writer); - // try writer.writeAll("headers: "); - // try headers.leak().writeFormat(formatter, writer, comptime enable_ansi_colors); - // try writer.writeAll("\n"); - // } - - try formatter.writeIndent(Writer, writer); - try writer.writeAll("status: "); - formatter.printAs(.Double, Writer, writer, JSC.JSValue.jsNumber(this.init.status_code), .NumberObject, enable_ansi_colors); - } - - pub fn deinit(this: *Body, _: std.mem.Allocator) void { - if (this.init.headers) |headers| { - headers.deref(); - this.init.headers = null; - } - this.value.deinit(); - } - - pub const Init = struct { - headers: ?*FetchHeaders = null, - status_code: u16, - method: Method = Method.GET, - - pub fn clone(this: Init, _: *JSGlobalObject) Init { - var that = this; - var headers = this.headers; - if (headers) |head| { - that.headers = head.cloneThis(); - } - - return that; - } - - pub fn init(_: std.mem.Allocator, ctx: js.JSContextRef, init_ref: js.JSValueRef) !?Init { - var result = Init{ .status_code = 200 }; - var array = js.JSObjectCopyPropertyNames(ctx, init_ref); - defer js.JSPropertyNameArrayRelease(array); - const count = js.JSPropertyNameArrayGetCount(array); - - var i: usize = 0; - while (i < count) : (i += 1) { - var property_name_ref = js.JSPropertyNameArrayGetNameAtIndex(array, i); - switch (js.JSStringGetLength(property_name_ref)) { - "headers".len => { - if (js.JSStringIsEqualToUTF8CString(property_name_ref, "headers")) { - // only support headers as an object for now. - if (js.JSObjectGetProperty(ctx, init_ref, property_name_ref, null)) |header_prop| { - const header_val = JSValue.fromRef(header_prop); - if (header_val.as(FetchHeaders)) |orig| { - result.headers = orig.cloneThis(); - } else { - result.headers = FetchHeaders.createFromJS(ctx.ptr(), header_val); - } - } - } - }, - - "method".len => { - if (js.JSStringIsEqualToUTF8CString(property_name_ref, "status")) { - var value_ref = js.JSObjectGetProperty(ctx, init_ref, property_name_ref, null); - var exception: js.JSValueRef = null; - const number = js.JSValueToNumber(ctx, value_ref, &exception); - if (exception != null or !std.math.isFinite(number)) continue; - result.status_code = @truncate(u16, @floatToInt(u64, number)); - } else if (js.JSStringIsEqualToUTF8CString(property_name_ref, "method")) { - result.method = Method.which( - JSC.JSValue.fromRef(init_ref).get(ctx.ptr(), "method").?.getZigString(ctx.ptr()).slice(), - ) orelse Method.GET; - } - }, - else => {}, - } - } - - if (result.headers == null and result.status_code < 200) return null; - return result; - } - }; - - pub const PendingValue = struct { - promise: ?JSValue = null, - readable: ?JSC.WebCore.ReadableStream = null, - // writable: JSC.WebCore.Sink - - global: *JSGlobalObject, - task: ?*anyopaque = null, - /// runs after the data is available. - callback: ?fn (ctx: *anyopaque, value: *Value) void = null, - /// conditionally runs when requesting data - /// used in HTTP server to ignore request bodies unless asked for it - onPull: ?fn (ctx: *anyopaque) void = null, - deinit: bool = false, - action: Action = Action.none, - - pub fn setPromise(value: *PendingValue, globalThis: *JSC.JSGlobalObject, action: Action) JSValue { - value.action = action; - - if (value.readable) |*readable| { - // switch (readable.ptr) { - // .JavaScript - // } - switch (action) { - .getText, .getJSON, .getBlob, .getArrayBuffer => { - switch (readable.ptr) { - .Blob => unreachable, - else => {}, - } - value.promise = switch (action) { - .getJSON => globalThis.readableStreamToJSON(readable.value), - .getArrayBuffer => globalThis.readableStreamToArrayBuffer(readable.value), - .getText => globalThis.readableStreamToText(readable.value), - .getBlob => globalThis.readableStreamToBlob(readable.value), - else => unreachable, - }; - value.promise.?.ensureStillAlive(); - readable.value.unprotect(); - - // js now owns the memory - value.readable = null; - - return value.promise.?; - }, - .none => {}, - } - } - - { - var promise = JSC.JSPromise.create(globalThis); - const promise_value = promise.asValue(globalThis); - value.promise = promise_value; - - if (value.onPull) |onPull| { - value.onPull = null; - onPull(value.task.?); - } - return promise_value; - } - } - - pub const Action = enum { - none, - getText, - getJSON, - getArrayBuffer, - getBlob, - }; - }; - - pub const Value = union(Tag) { - Blob: Blob, - Locked: PendingValue, - Used: void, - Empty: void, - Error: JSValue, - - pub const Tag = enum { - Blob, - Locked, - Used, - Empty, - Error, - }; - - pub const empty = Value{ .Empty = .{} }; - - pub fn fromReadableStream(readable: JSC.WebCore.ReadableStream, globalThis: *JSGlobalObject) Value { - if (readable.isLocked(globalThis)) { - return .{ .Error = ZigString.init("Cannot use a locked ReadableStream").toErrorInstance(globalThis) }; - } - - readable.value.protect(); - return .{ - .Locked = .{ - .readable = readable, - .global = globalThis, - }, - }; - } - - pub fn resolve(this: *Value, new: *Value, global: *JSGlobalObject) void { - if (this.* == .Locked) { - var locked = &this.Locked; - if (locked.readable) |readable| { - readable.done(); - locked.readable = null; - } - - if (locked.callback) |callback| { - locked.callback = null; - callback(locked.task.?, new); - } - - if (locked.promise) |promise| { - locked.promise = null; - var blob = new.use(); - - switch (locked.action) { - .getText => { - promise.asPromise().?.resolve(global, JSValue.fromRef(blob.getTextTransfer(global.ref()))); - }, - .getJSON => { - promise.asPromise().?.resolve(global, blob.toJSON(global, .share)); - blob.detach(); - }, - .getArrayBuffer => { - promise.asPromise().?.resolve(global, JSValue.fromRef(blob.getArrayBufferTransfer(global.ref()))); - }, - .getBlob => { - var ptr = bun.default_allocator.create(Blob) catch unreachable; - ptr.* = blob; - ptr.allocator = bun.default_allocator; - promise.asPromise().?.resolve(global, JSC.JSValue.fromRef(Blob.Class.make(global.ref(), ptr))); - }, - else => { - var ptr = bun.default_allocator.create(Blob) catch unreachable; - ptr.* = blob; - ptr.allocator = bun.default_allocator; - promise.asInternalPromise().?.resolve(global, JSC.JSValue.fromRef(Blob.Class.make(global.ref(), ptr))); - }, - } - JSC.C.JSValueUnprotect(global.ref(), promise.asObjectRef()); - } - } - } - pub fn slice(this: Value) []const u8 { - return switch (this) { - .Blob => this.Blob.sharedView(), - else => "", - }; - } - - pub fn use(this: *Value) Blob { - switch (this.*) { - .Blob => { - var new_blob = this.Blob; - std.debug.assert(new_blob.allocator == null); // owned by Body - this.* = .{ .Used = .{} }; - return new_blob; - }, - else => { - return Blob.initEmpty(undefined); - }, - } - } - - pub fn toErrorInstance(this: *Value, error_instance: JSC.JSValue, global: *JSGlobalObject) void { - if (this.* == .Locked) { - var locked = this.Locked; - locked.deinit = true; - if (locked.promise) |promise| { - if (promise.asInternalPromise()) |internal| { - internal.reject(global, error_instance); - } else if (promise.asPromise()) |internal| { - internal.reject(global, error_instance); - } - JSC.C.JSValueUnprotect(global.ref(), promise.asObjectRef()); - locked.promise = null; - } - - if (locked.readable) |readable| { - readable.done(); - locked.readable = null; - } - - this.* = .{ .Error = error_instance }; - if (locked.callback) |callback| { - locked.callback = null; - callback(locked.task.?, this); - } - return; - } - - this.* = .{ .Error = error_instance }; - } - - pub fn toErrorString(this: *Value, comptime err: string, global: *JSGlobalObject) void { - var error_str = ZigString.init(err); - var error_instance = error_str.toErrorInstance(global); - return this.toErrorInstance(error_instance, global); - } - - pub fn toError(this: *Value, err: anyerror, global: *JSGlobalObject) void { - var error_str = ZigString.init(std.fmt.allocPrint( - bun.default_allocator, - "Error reading file {s}", - .{@errorName(err)}, - ) catch unreachable); - error_str.mark(); - var error_instance = error_str.toErrorInstance(global); - return this.toErrorInstance(error_instance, global); - } - - pub fn deinit(this: *Value) void { - const tag = @as(Tag, this.*); - if (tag == .Locked) { - if (this.Locked.readable) |*readable| { - readable.done(); - } - - this.Locked.deinit = true; - return; - } - - if (tag == .Blob) { - this.Blob.deinit(); - this.* = Value.empty; - } - - if (tag == .Error) { - JSC.C.JSValueUnprotect(VirtualMachine.vm.global.ref(), this.Error.asObjectRef()); - } - } - - pub fn clone(this: Value, _: std.mem.Allocator) Value { - if (this == .Blob) { - return Value{ .Blob = this.Blob.dupe() }; - } - - return Value{ .Empty = .{} }; - } - }; - - pub fn @"404"(_: js.JSContextRef) Body { - return Body{ - .init = Init{ - .headers = null, - .status_code = 404, - }, - .value = Value.empty, - }; - } - - pub fn @"200"(_: js.JSContextRef) Body { - return Body{ - .init = Init{ - .status_code = 200, - }, - .value = Value.empty, - }; - } - - pub fn extract(ctx: js.JSContextRef, body_ref: js.JSObjectRef, exception: js.ExceptionRef) Body { - return extractBody( - ctx, - body_ref, - false, - null, - exception, - ); - } - - pub fn extractWithInit(ctx: js.JSContextRef, body_ref: js.JSObjectRef, init_ref: js.JSValueRef, exception: js.ExceptionRef) Body { - return extractBody( - ctx, - body_ref, - true, - init_ref, - exception, - ); - } - - // https://github.com/WebKit/webkit/blob/main/Source/WebCore/Modules/fetch/FetchBody.cpp#L45 - inline fn extractBody( - ctx: js.JSContextRef, - body_ref: js.JSObjectRef, - comptime has_init: bool, - init_ref: js.JSValueRef, - exception: js.ExceptionRef, - ) Body { - var body = Body{ - .init = Init{ .headers = null, .status_code = 200 }, - }; - const value = JSC.JSValue.fromRef(body_ref); - var allocator = getAllocator(ctx); - - if (comptime has_init) { - if (Init.init(allocator, ctx, init_ref.?)) |maybeInit| { - if (maybeInit) |init_| { - body.init = init_; - } - } else |_| {} - } - - if (JSC.WebCore.ReadableStream.fromJS(value, ctx)) |readable| { - switch (readable.ptr) { - .Blob => |blob| { - body.value = .{ - .Blob = Blob.initWithStore(blob.store, ctx), - }; - blob.store.ref(); - - readable.done(); - - if (!blob.done) { - blob.done = true; - blob.deinit(); - } - return body; - }, - else => {}, - } - - body.value = Body.Value.fromReadableStream(readable, ctx); - return body; - } - - body.value = .{ - .Blob = Blob.fromJS(ctx.ptr(), value, true, false) catch |err| { - if (err == error.InvalidArguments) { - JSC.JSError(allocator, "Expected an Array", .{}, ctx, exception); - return body; - } - - JSC.JSError(allocator, "Out of memory", .{}, ctx, exception); - return body; - }, - }; - - std.debug.assert(body.value.Blob.allocator == null); // owned by Body - - return body; - } -}; - -// https://developer.mozilla.org/en-US/docs/Web/API/Request -pub const Request = struct { - url: ZigString = ZigString.Empty, - headers: ?*FetchHeaders = null, - body: Body.Value = Body.Value{ .Empty = .{} }, - method: Method = Method.GET, - uws_request: ?*uws.Request = null, - - pub fn fromRequestContext(ctx: *RequestContext, global: *JSGlobalObject) !Request { - var req = Request{ - .url = ZigString.init(std.mem.span(ctx.getFullURL())), - .body = Body.Value.empty, - .method = ctx.method, - .headers = FetchHeaders.createFromPicoHeaders(global, ctx.request.headers), - }; - req.url.mark(); - return req; - } - - pub fn mimeType(this: *const Request) string { - if (this.headers) |headers| { - // Remember, we always lowercase it - // hopefully doesn't matter here tho - if (headers.get("content-type")) |content_type| { - return content_type; - } - } - - switch (this.body) { - .Blob => |blob| { - if (blob.content_type.len > 0) { - return blob.content_type; - } - - return MimeType.other.value; - }, - .Error, .Used, .Locked, .Empty => return MimeType.other.value, - } - } - - pub const Constructor = JSC.NewConstructor( - Request, - .{ - .constructor = .{ .rfn = constructor }, - }, - .{}, - ); - - pub const Class = NewClass( - Request, - .{ - .name = "Request", - .read_only = true, - }, - .{ - .finalize = finalize, - .text = .{ - .rfn = Request.getText, - }, - .json = .{ - .rfn = Request.getJSON, - }, - .arrayBuffer = .{ - .rfn = Request.getArrayBuffer, - }, - .blob = .{ - .rfn = Request.getBlob, - }, - .clone = .{ - .rfn = Request.doClone, - }, - }, - .{ - .@"cache" = .{ - .@"get" = getCache, - .@"ro" = true, - }, - .@"credentials" = .{ - .@"get" = getCredentials, - .@"ro" = true, - }, - .@"destination" = .{ - .@"get" = getDestination, - .@"ro" = true, - }, - .@"headers" = .{ - .@"get" = getHeaders, - .@"ro" = true, - }, - .@"integrity" = .{ - .@"get" = getIntegrity, - .@"ro" = true, - }, - .@"method" = .{ - .@"get" = getMethod, - .@"ro" = true, - }, - .@"mode" = .{ - .@"get" = getMode, - .@"ro" = true, - }, - .@"redirect" = .{ - .@"get" = getRedirect, - .@"ro" = true, - }, - .@"referrer" = .{ - .@"get" = getReferrer, - .@"ro" = true, - }, - .@"referrerPolicy" = .{ - .@"get" = getReferrerPolicy, - .@"ro" = true, - }, - .@"url" = .{ - .@"get" = getUrl, - .@"ro" = true, - }, - .@"bodyUsed" = .{ - .@"get" = getBodyUsed, - .@"ro" = true, - }, - }, - ); - - pub fn getCache( - _: *Request, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSStringRef, - _: js.ExceptionRef, - ) js.JSValueRef { - return js.JSValueMakeString(ctx, ZigString.init(Properties.UTF8.default).toValueGC(ctx.ptr()).asRef()); - } - pub fn getCredentials( - _: *Request, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSStringRef, - _: js.ExceptionRef, - ) js.JSValueRef { - return js.JSValueMakeString(ctx, ZigString.init(Properties.UTF8.include).toValueGC(ctx.ptr()).asRef()); - } - pub fn getDestination( - _: *Request, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSStringRef, - _: js.ExceptionRef, - ) js.JSValueRef { - return js.JSValueMakeString(ctx, ZigString.init("").toValueGC(ctx.ptr()).asRef()); - } - - pub fn getIntegrity( - _: *Request, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSStringRef, - _: js.ExceptionRef, - ) js.JSValueRef { - return ZigString.Empty.toValueGC(ctx.ptr()).asRef(); - } - pub fn getMethod( - this: *Request, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSStringRef, - _: js.ExceptionRef, - ) js.JSValueRef { - const string_contents: string = switch (this.method) { - .GET => Properties.UTF8.GET, - .HEAD => Properties.UTF8.HEAD, - .PATCH => Properties.UTF8.PATCH, - .PUT => Properties.UTF8.PUT, - .POST => Properties.UTF8.POST, - .OPTIONS => Properties.UTF8.OPTIONS, - else => "", - }; - - return ZigString.init(string_contents).toValue(ctx.ptr()).asRef(); - } - - pub fn getMode( - _: *Request, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSStringRef, - _: js.ExceptionRef, - ) js.JSValueRef { - return ZigString.init(Properties.UTF8.navigate).toValue(ctx.ptr()).asRef(); - } - - pub fn finalize(this: *Request) void { - if (this.headers) |headers| { - headers.deref(); - this.headers = null; - } - - if (this.url.isGloballyAllocated()) { - bun.default_allocator.free(bun.constStrToU8(this.url.slice())); - } - - bun.default_allocator.destroy(this); - } - - pub fn getRedirect( - _: *Request, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSStringRef, - _: js.ExceptionRef, - ) js.JSValueRef { - return ZigString.init(Properties.UTF8.follow).toValueGC(ctx.ptr()).asRef(); - } - pub fn getReferrer( - this: *Request, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSStringRef, - _: js.ExceptionRef, - ) js.JSValueRef { - if (this.headers) |headers_ref| { - if (headers_ref.get("referrer")) |referrer| { - return ZigString.init(referrer).toValueGC(ctx.ptr()).asRef(); - } - } - - return ZigString.init("").toValueGC(ctx.ptr()).asRef(); - } - pub fn getReferrerPolicy( - _: *Request, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSStringRef, - _: js.ExceptionRef, - ) js.JSValueRef { - return ZigString.init("").toValueGC(ctx.ptr()).asRef(); - } - pub fn getUrl( - this: *Request, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSStringRef, - _: js.ExceptionRef, - ) js.JSValueRef { - return this.url.toValueGC(ctx.ptr()).asObjectRef(); - } - - pub fn constructor( - ctx: js.JSContextRef, - _: js.JSObjectRef, - arguments: []const js.JSValueRef, - exception: js.ExceptionRef, - ) js.JSObjectRef { - var request = Request{}; - - switch (arguments.len) { - 0 => {}, - 1 => { - request.url = JSC.JSValue.fromRef(arguments[0]).getZigString(ctx.ptr()); - }, - else => { - request.url = JSC.JSValue.fromRef(arguments[0]).getZigString(ctx.ptr()); - - if (Body.Init.init(getAllocator(ctx), ctx, arguments[1]) catch null) |req_init| { - request.headers = req_init.headers; - request.method = req_init.method; - } - - if (JSC.JSValue.fromRef(arguments[1]).get(ctx.ptr(), "body")) |body_| { - if (Blob.fromJS(ctx.ptr(), body_, true, false)) |blob| { - if (blob.size > 0) { - request.body = Body.Value{ .Blob = blob }; - } - } else |err| { - if (err == error.InvalidArguments) { - JSC.JSError(getAllocator(ctx), "Expected an Array", .{}, ctx, exception); - return null; - } - - JSC.JSError(getAllocator(ctx), "Invalid Body", .{}, ctx, exception); - return null; - } - } - }, - } - - var request_ = getAllocator(ctx).create(Request) catch unreachable; - request_.* = request; - return Request.Class.make( - ctx, - request_, - ); - } - - pub fn getBodyValue( - this: *Request, - ) *Body.Value { - return &this.body; - } - - pub fn getBodyUsed( - this: *Request, - _: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSStringRef, - _: js.ExceptionRef, - ) js.JSValueRef { - return JSC.JSValue.jsBoolean(this.body == .Used).asRef(); - } - - pub usingnamespace BlobInterface(@This()); - - pub fn doClone( - this: *Request, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - _: []const js.JSValueRef, - _: js.ExceptionRef, - ) js.JSValueRef { - var cloned = this.clone(getAllocator(ctx), ctx.ptr()); - return Request.Class.make(ctx, cloned); - } - - pub fn getHeaders( - this: *Request, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSStringRef, - _: js.ExceptionRef, - ) js.JSValueRef { - if (this.headers == null) { - if (this.uws_request) |req| { - this.headers = FetchHeaders.createFromUWS(ctx.ptr(), req); - } else { - this.headers = FetchHeaders.createEmpty(); - } - } - - return this.headers.?.toJS(ctx.ptr()).asObjectRef(); - } - - pub fn cloneInto( - this: *const Request, - req: *Request, - allocator: std.mem.Allocator, - globalThis: *JSGlobalObject, - ) void { - req.* = Request{ - .body = this.body.clone(allocator), - .url = ZigString.init(allocator.dupe(u8, this.url.slice()) catch unreachable), - .method = this.method, - }; - if (this.headers) |head| { - req.headers = head.cloneThis(); - } else if (this.uws_request) |uws_req| { - req.headers = FetchHeaders.createFromUWS(globalThis, uws_req); - } - } - - pub fn clone(this: *const Request, allocator: std.mem.Allocator, globalThis: *JSGlobalObject) *Request { - var req = allocator.create(Request) catch unreachable; - this.cloneInto(req, allocator, globalThis); - return req; - } -}; - -fn BlobInterface(comptime Type: type) type { - return struct { - pub fn getText( - this: *Type, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - _: []const js.JSValueRef, - _: js.ExceptionRef, - ) js.JSValueRef { - var value: *Body.Value = this.getBodyValue(); - if (value.* == .Locked) { - return value.Locked.setPromise(ctx.ptr(), .getText).asObjectRef(); - } - - var blob = this.body.use(); - return blob.getTextTransfer(ctx); - } - - pub fn getJSON( - this: *Type, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - _: []const js.JSValueRef, - exception: js.ExceptionRef, - ) js.JSValueRef { - var value: *Body.Value = this.getBodyValue(); - if (value.* == .Locked) { - return value.Locked.setPromise(ctx.ptr(), .getJSON).asObjectRef(); - } - - var blob = this.body.use(); - return blob.getJSON(ctx, null, null, &.{}, exception); - } - pub fn getArrayBuffer( - this: *Type, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - _: []const js.JSValueRef, - _: js.ExceptionRef, - ) js.JSValueRef { - var value: *Body.Value = this.getBodyValue(); - - if (value.* == .Locked) { - return value.Locked.setPromise(ctx.ptr(), .getArrayBuffer).asObjectRef(); - } - - var blob = this.body.use(); - return blob.getArrayBufferTransfer(ctx); - } - - pub fn getBlob( - this: *Type, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - _: []const js.JSValueRef, - _: js.ExceptionRef, - ) js.JSValueRef { - var value: *Body.Value = this.getBodyValue(); - - if (value.* == .Locked) { - return value.Locked.setPromise(ctx.ptr(), .getBlob).asObjectRef(); - } - - var blob = this.body.use(); - var ptr = getAllocator(ctx).create(Blob) catch unreachable; - ptr.* = blob; - blob.allocator = getAllocator(ctx); - return JSC.JSPromise.resolvedPromiseValue(ctx.ptr(), JSValue.fromRef(Blob.Class.make(ctx, ptr))).asObjectRef(); - } - - // pub fn getBody( - // this: *Type, - // ctx: js.JSContextRef, - // _: js.JSObjectRef, - // _: js.JSObjectRef, - // _: []const js.JSValueRef, - // _: js.ExceptionRef, - // ) js.JSValueRef { - // var value: *Body.Value = this.getBodyValue(); - - // switch (value.*) { - // .Empty => {}, - // } - // } - }; -} - -// https://github.com/WebKit/WebKit/blob/main/Source/WebCore/workers/service/FetchEvent.h -pub const FetchEvent = struct { - started_waiting_at: u64 = 0, - response: ?*Response = null, - request_context: ?*RequestContext = null, - request: Request, - pending_promise: ?*JSInternalPromise = null, - - onPromiseRejectionCtx: *anyopaque = undefined, - onPromiseRejectionHandler: ?fn (ctx: *anyopaque, err: anyerror, fetch_event: *FetchEvent, value: JSValue) void = null, - rejected: bool = false, - - pub const Class = NewClass( - FetchEvent, - .{ - .name = "FetchEvent", - .read_only = true, - .ts = .{ .class = d.ts.class{ .interface = true } }, - }, - .{ - .@"respondWith" = .{ - .rfn = respondWith, - .ts = d.ts{ - .tsdoc = "Render the response in the active HTTP request", - .@"return" = "void", - .args = &[_]d.ts.arg{ - .{ .name = "response", .@"return" = "Response" }, - }, - }, - }, - .@"waitUntil" = waitUntil, - .finalize = finalize, - }, - .{ - .@"client" = .{ - .@"get" = getClient, - .ro = true, - .ts = d.ts{ - .tsdoc = "HTTP client metadata. This is not implemented yet, do not use.", - .@"return" = "undefined", - }, - }, - .@"request" = .{ - .@"get" = getRequest, - .ro = true, - .ts = d.ts{ - .tsdoc = "HTTP request", - .@"return" = "InstanceType<Request>", - }, - }, - }, - ); - - pub fn finalize( - this: *FetchEvent, - ) void { - VirtualMachine.vm.allocator.destroy(this); - } - - pub fn getClient( - _: *FetchEvent, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSStringRef, - _: js.ExceptionRef, - ) js.JSValueRef { - Output.prettyErrorln("FetchEvent.client is not implemented yet - sorry!!", .{}); - Output.flush(); - return js.JSValueMakeUndefined(ctx); - } - pub fn getRequest( - this: *FetchEvent, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSStringRef, - _: js.ExceptionRef, - ) js.JSValueRef { - var req = bun.default_allocator.create(Request) catch unreachable; - req.* = this.request; - - return Request.Class.make( - ctx, - req, - ); - } - - // https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent/respondWith - pub fn respondWith( - this: *FetchEvent, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - arguments: []const js.JSValueRef, - exception: js.ExceptionRef, - ) js.JSValueRef { - var request_context = this.request_context orelse return js.JSValueMakeUndefined(ctx); - if (request_context.has_called_done) return js.JSValueMakeUndefined(ctx); - var globalThis = ctx.ptr(); - - // A Response or a Promise that resolves to a Response. Otherwise, a network error is returned to Fetch. - if (arguments.len == 0 or !Response.Class.isLoaded() or !js.JSValueIsObject(ctx, arguments[0])) { - JSError(getAllocator(ctx), "event.respondWith() must be a Response or a Promise<Response>.", .{}, ctx, exception); - request_context.sendInternalError(error.respondWithWasEmpty) catch {}; - return js.JSValueMakeUndefined(ctx); - } - - var arg = arguments[0]; - - if (JSValue.fromRef(arg).as(Response) == null) { - this.pending_promise = this.pending_promise orelse JSInternalPromise.resolvedPromise(globalThis, JSValue.fromRef(arguments[0])); - } - - if (this.pending_promise) |promise| { - VirtualMachine.vm.event_loop.waitForPromise(promise); - - switch (promise.status(ctx.ptr().vm())) { - .Fulfilled => {}, - else => { - this.rejected = true; - this.pending_promise = null; - this.onPromiseRejectionHandler.?( - this.onPromiseRejectionCtx, - error.PromiseRejection, - this, - promise.result(globalThis.vm()), - ); - return js.JSValueMakeUndefined(ctx); - }, - } - - arg = promise.result(ctx.ptr().vm()).asRef(); - } - - var response: *Response = GetJSPrivateData(Response, arg) orelse { - this.rejected = true; - this.pending_promise = null; - JSError(getAllocator(ctx), "event.respondWith() expects Response or Promise<Response>", .{}, ctx, exception); - this.onPromiseRejectionHandler.?(this.onPromiseRejectionCtx, error.RespondWithInvalidTypeInternal, this, JSValue.fromRef(exception.*)); - return js.JSValueMakeUndefined(ctx); - }; - - defer { - if (!VirtualMachine.vm.had_errors) { - Output.printElapsed(@intToFloat(f64, (request_context.timer.lap())) / std.time.ns_per_ms); - - Output.prettyError( - " <b>{s}<r><d> - <b>{d}<r> <d>transpiled, <d><b>{d}<r> <d>imports<r>\n", - .{ - request_context.matched_route.?.name, - VirtualMachine.vm.transpiled_count, - VirtualMachine.vm.resolved_count, - }, - ); - } - } - - defer this.pending_promise = null; - var needs_mime_type = true; - var content_length: ?usize = null; - - if (response.body.init.headers) |headers_ref| { - var headers = Headers.from(headers_ref, request_context.allocator) catch unreachable; - - var i: usize = 0; - while (i < headers.entries.len) : (i += 1) { - var header = headers.entries.get(i); - const name = headers.asStr(header.name); - if (strings.eqlComptime(name, "content-type") and headers.asStr(header.value).len > 0) { - needs_mime_type = false; - } - - if (strings.eqlComptime(name, "content-length")) { - content_length = std.fmt.parseInt(usize, headers.asStr(header.value), 10) catch null; - continue; - } - - // Some headers need to be managed by bun - if (strings.eqlComptime(name, "transfer-encoding") or - strings.eqlComptime(name, "content-encoding") or - strings.eqlComptime(name, "strict-transport-security") or - strings.eqlComptime(name, "content-security-policy")) - { - continue; - } - - request_context.appendHeaderSlow( - name, - headers.asStr(header.value), - ) catch unreachable; - } - } - - if (needs_mime_type) { - request_context.appendHeader("Content-Type", response.mimeTypeWithDefault(MimeType.html, request_context)); - } - - var blob = response.body.value.use(); - defer blob.deinit(); - - const content_length_ = content_length orelse blob.size; - - if (content_length_ == 0) { - request_context.sendNoContent() catch return js.JSValueMakeUndefined(ctx); - return js.JSValueMakeUndefined(ctx); - } - - if (FeatureFlags.strong_etags_for_built_files) { - const did_send = request_context.writeETag(blob.sharedView()) catch false; - if (did_send) { - // defer getAllocator(ctx).destroy(str.ptr); - return js.JSValueMakeUndefined(ctx); - } - } - - defer request_context.done(); - - request_context.writeStatusSlow(response.body.init.status_code) catch return js.JSValueMakeUndefined(ctx); - request_context.prepareToSendBody(content_length_, false) catch return js.JSValueMakeUndefined(ctx); - - request_context.writeBodyBuf(blob.sharedView()) catch return js.JSValueMakeUndefined(ctx); - - return js.JSValueMakeUndefined(ctx); - } - - // our implementation of the event listener already does this - // so this is a no-op for us - pub fn waitUntil( - _: *FetchEvent, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - _: []const js.JSValueRef, - _: js.ExceptionRef, - ) js.JSValueRef { - return js.JSValueMakeUndefined(ctx); - } -}; diff --git a/src/javascript/jsc/webcore/streams.zig b/src/javascript/jsc/webcore/streams.zig deleted file mode 100644 index df5de24fa..000000000 --- a/src/javascript/jsc/webcore/streams.zig +++ /dev/null @@ -1,2208 +0,0 @@ -const std = @import("std"); -const Api = @import("../../../api/schema.zig").Api; -const bun = @import("../../../global.zig"); -const RequestContext = @import("../../../http.zig").RequestContext; -const MimeType = @import("../../../http.zig").MimeType; -const ZigURL = @import("../../../url.zig").URL; -const HTTPClient = @import("http"); -const NetworkThread = HTTPClient.NetworkThread; -const AsyncIO = NetworkThread.AsyncIO; -const JSC = @import("javascript_core"); -const js = JSC.C; - -const Method = @import("../../../http/method.zig").Method; -const FetchHeaders = JSC.FetchHeaders; -const ObjectPool = @import("../../../pool.zig").ObjectPool; -const SystemError = JSC.SystemError; -const Output = @import("../../../global.zig").Output; -const MutableString = @import("../../../global.zig").MutableString; -const strings = @import("../../../global.zig").strings; -const string = @import("../../../global.zig").string; -const default_allocator = @import("../../../global.zig").default_allocator; -const FeatureFlags = @import("../../../global.zig").FeatureFlags; -const ArrayBuffer = @import("../base.zig").ArrayBuffer; -const Properties = @import("../base.zig").Properties; -const NewClass = @import("../base.zig").NewClass; -const d = @import("../base.zig").d; -const castObj = @import("../base.zig").castObj; -const getAllocator = @import("../base.zig").getAllocator; -const JSPrivateDataPtr = @import("../base.zig").JSPrivateDataPtr; -const GetJSPrivateData = @import("../base.zig").GetJSPrivateData; -const Environment = @import("../../../env.zig"); -const ZigString = JSC.ZigString; -const IdentityContext = @import("../../../identity_context.zig").IdentityContext; -const JSInternalPromise = JSC.JSInternalPromise; -const JSPromise = JSC.JSPromise; -const JSValue = JSC.JSValue; -const JSError = JSC.JSError; -const JSGlobalObject = JSC.JSGlobalObject; - -const VirtualMachine = @import("../javascript.zig").VirtualMachine; -const Task = JSC.Task; -const JSPrinter = @import("../../../js_printer.zig"); -const picohttp = @import("picohttp"); -const StringJoiner = @import("../../../string_joiner.zig"); -const uws = @import("uws"); -const Blob = JSC.WebCore.Blob; -const Response = JSC.WebCore.Response; -const Request = JSC.WebCore.Request; - -pub const ReadableStream = struct { - value: JSValue, - ptr: Source, - - pub fn done(this: *const ReadableStream) void { - this.value.unprotect(); - } - - pub const Tag = enum(i32) { - Invalid = -1, - - JavaScript = 0, - Blob = 1, - File = 2, - HTTPRequest = 3, - HTTPSRequest = 4, - HTTPResponse = 5, - HTTPSResponse = 6, - }; - pub const Source = union(Tag) { - Invalid: void, - JavaScript: void, - Blob: *ByteBlobLoader, - File: *FileBlobLoader, - // HTTPRequest: *HTTPRequest, - HTTPRequest: void, - // HTTPSRequest: *HTTPSRequest, - HTTPSRequest: void, - // HTTPRequest: *HTTPRequest, - HTTPResponse: void, - // HTTPSRequest: *HTTPSRequest, - HTTPSResponse: void, - }; - - extern fn ReadableStreamTag__tagged(globalObject: *JSGlobalObject, possibleReadableStream: JSValue, ptr: *JSValue) Tag; - extern fn ReadableStream__isDisturbed(possibleReadableStream: JSValue, globalObject: *JSGlobalObject) bool; - extern fn ReadableStream__isLocked(possibleReadableStream: JSValue, globalObject: *JSGlobalObject) bool; - extern fn ReadableStream__empty(*JSGlobalObject) JSC.JSValue; - extern fn ReadableStream__fromBlob( - *JSGlobalObject, - store: *anyopaque, - offset: usize, - length: usize, - ) JSC.JSValue; - - pub fn isDisturbed(this: *const ReadableStream, globalObject: *JSGlobalObject) bool { - JSC.markBinding(); - return ReadableStream__isDisturbed(this.value, globalObject); - } - - pub fn isLocked(this: *const ReadableStream, globalObject: *JSGlobalObject) bool { - JSC.markBinding(); - return ReadableStream__isLocked(this.value, globalObject); - } - - pub fn fromJS(value: JSValue, globalThis: *JSGlobalObject) ?ReadableStream { - JSC.markBinding(); - var ptr = JSValue.zero; - return switch (ReadableStreamTag__tagged(globalThis, value, &ptr)) { - .JavaScript => ReadableStream{ - .value = value, - .ptr = .{ - .JavaScript = {}, - }, - }, - .Blob => ReadableStream{ - .value = value, - .ptr = .{ - .Blob = ptr.asPtr(ByteBlobLoader), - }, - }, - .File => ReadableStream{ - .value = value, - .ptr = .{ - .File = ptr.asPtr(FileBlobLoader), - }, - }, - - // .HTTPRequest => ReadableStream{ - // .value = value, - // .ptr = .{ - // .HTTPRequest = ptr.asPtr(HTTPRequest), - // }, - // }, - // .HTTPSRequest => ReadableStream{ - // .value = value, - // .ptr = .{ - // .HTTPSRequest = ptr.asPtr(HTTPSRequest), - // }, - // }, - else => null, - }; - } - - extern fn ZigGlobalObject__createNativeReadableStream(*JSGlobalObject, nativePtr: JSValue, nativeType: JSValue) JSValue; - - pub fn fromNative(globalThis: *JSGlobalObject, id: Tag, ptr: *anyopaque) JSC.JSValue { - return ZigGlobalObject__createNativeReadableStream(globalThis, JSValue.fromPtr(ptr), JSValue.jsNumber(@enumToInt(id))); - } - pub fn fromBlob(globalThis: *JSGlobalObject, blob: *const Blob, recommended_chunk_size: Blob.SizeType) JSC.JSValue { - if (comptime JSC.is_bindgen) - unreachable; - var store = blob.store orelse { - return ReadableStream.empty(globalThis); - }; - switch (store.data) { - .bytes => { - var reader = bun.default_allocator.create(ByteBlobLoader.Source) catch unreachable; - reader.* = .{ - .context = undefined, - }; - reader.context.setup(blob, recommended_chunk_size); - return reader.toJS(globalThis); - }, - .file => { - var reader = bun.default_allocator.create(FileBlobLoader.Source) catch unreachable; - reader.* = .{ - .context = undefined, - }; - reader.context.setup(store, recommended_chunk_size); - return reader.toJS(globalThis); - }, - } - } - - pub fn empty(globalThis: *JSGlobalObject) JSC.JSValue { - if (comptime JSC.is_bindgen) - unreachable; - - return ReadableStream__empty(globalThis); - } - - const Base = @import("../../../ast/base.zig"); - pub const StreamTag = enum(usize) { - invalid = 0, - _, - - pub fn init(filedes: JSC.Node.FileDescriptor) StreamTag { - var bytes = [8]u8{ 1, 0, 0, 0, 0, 0, 0, 0 }; - const filedes_ = @bitCast([8]u8, @as(usize, @truncate(u56, @intCast(usize, filedes)))); - bytes[1..8].* = filedes_[0..7].*; - - return @intToEnum(StreamTag, @bitCast(u64, bytes)); - } - - pub fn fd(this: StreamTag) JSC.Node.FileDescriptor { - var bytes = @bitCast([8]u8, @enumToInt(this)); - if (bytes[0] != 1) { - return std.math.maxInt(JSC.Node.FileDescriptor); - } - var out: u64 = 0; - @bitCast([8]u8, out)[0..7].* = bytes[1..8].*; - return @intCast(JSC.Node.FileDescriptor, out); - } - }; -}; - -pub const StreamStart = union(Tag) { - empty: void, - err: JSC.Node.Syscall.Error, - chunk_size: Blob.SizeType, - ArrayBufferSink: struct { - chunk_size: Blob.SizeType, - as_uint8array: bool, - stream: bool, - }, - ready: void, - - pub const Tag = enum { - empty, - err, - chunk_size, - ArrayBufferSink, - ready, - }; - - pub fn toJS(this: StreamStart, globalThis: *JSGlobalObject) JSC.JSValue { - switch (this) { - .empty, .ready => { - return JSC.JSValue.jsUndefined(); - }, - .chunk_size => |chunk| { - return JSC.JSValue.jsNumber(@intCast(Blob.SizeType, chunk)); - }, - .err => |err| { - globalThis.vm().throwError(globalThis, err.toJSC(globalThis)); - return JSC.JSValue.jsUndefined(); - }, - else => { - return JSC.JSValue.jsUndefined(); - }, - } - } - - pub fn fromJS(globalThis: *JSGlobalObject, value: JSValue) StreamStart { - if (value.isEmptyOrUndefinedOrNull() or !value.isObject()) { - return .{ .empty = {} }; - } - - if (value.get(globalThis, "chunkSize")) |chunkSize| { - return .{ .chunk_size = @intCast(Blob.SizeType, @truncate(i52, chunkSize.toInt64())) }; - } - - return .{ .empty = {} }; - } - - pub fn fromJSWithTag( - globalThis: *JSGlobalObject, - value: JSValue, - comptime tag: Tag, - ) StreamStart { - if (value.isEmptyOrUndefinedOrNull() or !value.isObject()) { - return .{ .empty = {} }; - } - - switch (comptime tag) { - .ArrayBufferSink => { - var as_uint8array = false; - var stream = false; - var chunk_size: JSC.WebCore.Blob.SizeType = 0; - var empty = true; - - if (value.get(globalThis, "asUint8Array")) |as_array| { - as_uint8array = as_array.toBoolean(); - empty = false; - } - - if (value.get(globalThis, "stream")) |as_array| { - stream = as_array.toBoolean(); - empty = false; - } - - if (value.get(globalThis, "highWaterMark")) |chunkSize| { - empty = false; - chunk_size = @intCast(JSC.WebCore.Blob.SizeType, @maximum(0, @truncate(i51, chunkSize.toInt64()))); - } - - if (!empty) { - return .{ - .ArrayBufferSink = .{ - .chunk_size = chunk_size, - .as_uint8array = as_uint8array, - .stream = stream, - }, - }; - } - }, - else => @compileError("Unuspported tag"), - } - - return .{ .empty = {} }; - } -}; - -pub const StreamResult = union(Tag) { - owned: bun.ByteList, - owned_and_done: bun.ByteList, - temporary_and_done: bun.ByteList, - temporary: bun.ByteList, - into_array: IntoArray, - into_array_and_done: IntoArray, - pending: *Pending, - err: JSC.Node.Syscall.Error, - done: void, - - pub const Tag = enum { - owned, - owned_and_done, - temporary_and_done, - temporary, - into_array, - into_array_and_done, - pending, - err, - done, - }; - - pub fn slice(this: *const StreamResult) []const u8 { - return switch (this.*) { - .owned => |owned| owned.slice(), - .owned_and_done => |owned_and_done| owned_and_done.slice(), - .temporary_and_done => |temporary_and_done| temporary_and_done.slice(), - .temporary => |temporary| temporary.slice(), - else => "", - }; - } - - pub const Writable = union(StreamResult.Tag) { - pending: *Writable.Pending, - - err: JSC.Node.Syscall.Error, - done: void, - - owned: Blob.SizeType, - owned_and_done: Blob.SizeType, - temporary_and_done: Blob.SizeType, - temporary: Blob.SizeType, - into_array: Blob.SizeType, - into_array_and_done: Blob.SizeType, - - pub const Pending = struct { - frame: anyframe, - result: Writable, - consumed: Blob.SizeType = 0, - used: bool = false, - }; - - pub fn toPromised(globalThis: *JSGlobalObject, promise: *JSPromise, pending: *Writable.Pending) void { - var frame = bun.default_allocator.create(@Frame(Writable.toPromisedWrap)) catch unreachable; - frame.* = async Writable.toPromisedWrap(globalThis, promise, pending); - pending.frame = frame; - } - - pub fn isDone(this: *const Writable) bool { - return switch (this.*) { - .owned_and_done, .temporary_and_done, .into_array_and_done, .done, .err => true, - else => false, - }; - } - fn toPromisedWrap(globalThis: *JSGlobalObject, promise: *JSPromise, pending: *Writable.Pending) void { - suspend {} - - pending.used = true; - const result: Writable = pending.result; - - switch (result) { - .err => |err| { - promise.reject(globalThis, err.toJSC(globalThis)); - }, - .done => { - promise.resolve(globalThis, JSValue.jsBoolean(false)); - }, - else => { - promise.resolve(globalThis, result.toJS(globalThis)); - }, - } - } - - pub fn toJS(this: Writable, globalThis: *JSGlobalObject) JSValue { - return switch (this) { - .err => |err| JSC.JSPromise.rejectedPromise(globalThis, JSValue.c(err.toJS(globalThis.ref()))).asValue(globalThis), - - .owned => |len| JSC.JSValue.jsNumber(len), - .owned_and_done => |len| JSC.JSValue.jsNumber(len), - .temporary_and_done => |len| JSC.JSValue.jsNumber(len), - .temporary => |len| JSC.JSValue.jsNumber(len), - .into_array => |len| JSC.JSValue.jsNumber(len), - .into_array_and_done => |len| JSC.JSValue.jsNumber(len), - - // false == controller.close() - // undefined == noop, but we probably won't send it - .done => JSC.JSValue.jsBoolean(true), - - .pending => |pending| brk: { - var promise = JSC.JSPromise.create(globalThis); - Writable.toPromised(globalThis, promise, pending); - break :brk promise.asValue(globalThis); - }, - }; - } - }; - - pub const IntoArray = struct { - value: JSValue = JSValue.zero, - len: Blob.SizeType = std.math.maxInt(Blob.SizeType), - }; - - pub const Pending = struct { - frame: anyframe, - result: StreamResult, - used: bool = false, - }; - - pub fn isDone(this: *const StreamResult) bool { - return switch (this.*) { - .owned_and_done, .temporary_and_done, .into_array_and_done, .done, .err => true, - else => false, - }; - } - - fn toPromisedWrap(globalThis: *JSGlobalObject, promise: *JSPromise, pending: *Pending) void { - suspend {} - - pending.used = true; - const result: StreamResult = pending.result; - - switch (result) { - .err => |err| { - promise.reject(globalThis, err.toJSC(globalThis)); - }, - .done => { - promise.resolve(globalThis, JSValue.jsBoolean(false)); - }, - else => { - promise.resolve(globalThis, result.toJS(globalThis)); - }, - } - } - - pub fn toPromised(globalThis: *JSGlobalObject, promise: *JSPromise, pending: *Pending) void { - var frame = bun.default_allocator.create(@Frame(toPromisedWrap)) catch unreachable; - frame.* = async toPromisedWrap(globalThis, promise, pending); - pending.frame = frame; - } - - pub fn toJS(this: *const StreamResult, globalThis: *JSGlobalObject) JSValue { - switch (this.*) { - .owned => |list| { - return JSC.ArrayBuffer.fromBytes(list.slice(), .Uint8Array).toJS(globalThis.ref(), null); - }, - .owned_and_done => |list| { - return JSC.ArrayBuffer.fromBytes(list.slice(), .Uint8Array).toJS(globalThis.ref(), null); - }, - .temporary => |temp| { - var array = JSC.JSValue.createUninitializedUint8Array(globalThis, temp.len); - var slice_ = array.asArrayBuffer(globalThis).?.slice(); - @memcpy(slice_.ptr, temp.ptr, temp.len); - return array; - }, - .temporary_and_done => |temp| { - var array = JSC.JSValue.createUninitializedUint8Array(globalThis, temp.len); - var slice_ = array.asArrayBuffer(globalThis).?.slice(); - @memcpy(slice_.ptr, temp.ptr, temp.len); - return array; - }, - .into_array => |array| { - return JSC.JSValue.jsNumberFromInt64(array.len); - }, - .into_array_and_done => |array| { - return JSC.JSValue.jsNumberFromInt64(array.len); - }, - .pending => |pending| { - var promise = JSC.JSPromise.create(globalThis); - toPromised(globalThis, promise, pending); - return promise.asValue(globalThis); - }, - - .err => |err| { - return JSC.JSPromise.rejectedPromise(globalThis, JSValue.c(err.toJS(globalThis.ref()))).asValue(globalThis); - }, - - // false == controller.close() - // undefined == noop, but we probably won't send it - .done => { - return JSC.JSValue.jsBoolean(false); - }, - } - } -}; - -pub const Signal = struct { - ptr: *anyopaque = @intToPtr(*anyopaque, 0xaaaaaaaa), - vtable: VTable = VTable.Dead, - - pub fn isDead(this: Signal) bool { - return this.ptr == @intToPtr(*anyopaque, 0xaaaaaaaa); - } - - pub fn initWithType(comptime Type: type, handler: *Type) Sink { - return .{ - .ptr = handler, - .vtable = VTable.wrap(Type), - }; - } - - pub fn init(handler: anytype) Signal { - return initWithType(std.meta.Child(@TypeOf(handler)), handler); - } - - pub fn close(this: Signal, err: ?JSC.Node.Syscall.Error) void { - if (this.isDead()) - return; - this.vtable.close(this.ptr, err); - } - pub fn ready(this: Signal, amount: ?Blob.SizeType, offset: ?Blob.SizeType) void { - if (this.isDead()) - return; - this.vtable.ready(this.ptr, amount, offset); - } - pub fn start(this: Signal) void { - if (this.isDead()) - return; - this.vtable.start(this.ptr); - } - - pub const VTable = struct { - pub const OnCloseFn = fn (this: *anyopaque, err: ?JSC.Node.Syscall.Error) void; - pub const OnReadyFn = fn (this: *anyopaque, amount: ?Blob.SizeType, offset: ?Blob.SizeType) void; - pub const OnStartFn = fn (this: *anyopaque) void; - close: OnCloseFn, - ready: OnReadyFn, - start: OnStartFn, - - const DeadFns = struct { - pub fn close(_: *anyopaque, _: ?JSC.Node.Syscall.Error) void { - unreachable; - } - pub fn ready(_: *anyopaque, _: ?Blob.SizeType, _: ?Blob.SizeType) void { - unreachable; - } - - pub fn start(_: *anyopaque) void { - unreachable; - } - }; - - pub const Dead = VTable{ .close = DeadFns.close, .ready = DeadFns.ready, .start = DeadFns.start }; - - pub fn wrap( - comptime Wrapped: type, - ) VTable { - const Functions = struct { - fn onClose(this: *anyopaque, err: ?JSC.Node.Syscall.Error) void { - Wrapped.close(@ptrCast(*Wrapped, @alignCast(std.meta.alignment(Wrapped), this)), err); - } - fn onReady(this: *anyopaque, amount: ?Blob.SizeType, offset: ?Blob.SizeType) void { - Wrapped.ready(@ptrCast(*Wrapped, @alignCast(std.meta.alignment(Wrapped), this)), amount, offset); - } - fn onStart(this: *anyopaque) void { - Wrapped.start(@ptrCast(*Wrapped, @alignCast(std.meta.alignment(Wrapped), this))); - } - }; - - return VTable{ - .close = Functions.onClose, - .ready = Functions.onReady, - .start = Functions.onStart, - }; - } - }; -}; - -pub const Sink = struct { - ptr: *anyopaque, - vtable: VTable, - status: Status = Status.closed, - used: bool = false, - - pub const Status = enum { - ready, - closed, - }; - - pub const Data = union(enum) { - utf16: StreamResult, - latin1: StreamResult, - bytes: StreamResult, - }; - - pub fn initWithType(comptime Type: type, handler: *Type) Sink { - return .{ - .ptr = handler, - .vtable = VTable.wrap(Type), - .status = .ready, - .used = false, - }; - } - - pub fn init(handler: anytype) Sink { - return initWithType(std.meta.Child(@TypeOf(handler)), handler); - } - - pub const UTF8Fallback = struct { - const stack_size = 1024; - pub fn writeLatin1(comptime Ctx: type, ctx: *Ctx, input: StreamResult, comptime writeFn: anytype) StreamResult.Writable { - var str = input.slice(); - if (strings.isAllASCII(str)) { - return writeFn( - ctx, - input, - ); - } - - if (stack_size >= str.len) { - var buf: [stack_size]u8 = undefined; - @memcpy(&buf, str.ptr, str.len); - strings.replaceLatin1WithUTF8(buf[0..str.len]); - if (input.isDone()) { - const result = writeFn(ctx, .{ .temporary_and_done = bun.ByteList.init(buf[0..str.len]) }); - return result; - } else { - const result = writeFn(ctx, .{ .temporary = bun.ByteList.init(buf[0..str.len]) }); - return result; - } - } - - { - var slice = bun.default_allocator.alloc(u8, str.len) catch return .{ .err = JSC.Node.Syscall.Error.oom }; - @memcpy(slice.ptr, str.ptr, str.len); - strings.replaceLatin1WithUTF8(slice[0..str.len]); - if (input.isDone()) { - return writeFn(ctx, .{ .owned_and_done = bun.ByteList.init(slice) }); - } else { - return writeFn(ctx, .{ .owned = bun.ByteList.init(slice) }); - } - } - } - - pub fn writeUTF16(comptime Ctx: type, ctx: *Ctx, input: StreamResult, comptime writeFn: anytype) StreamResult.Writable { - var str: []const u16 = std.mem.bytesAsSlice(u16, input.slice()); - - if (stack_size >= str.len * 2) { - var buf: [stack_size]u8 = undefined; - const copied = strings.copyUTF16IntoUTF8(&buf, []const u16, str); - std.debug.assert(copied.written <= stack_size); - std.debug.assert(copied.read <= stack_size); - if (input.isDone()) { - const result = writeFn(ctx, .{ .temporary_and_done = bun.ByteList.init(buf[0..copied.written]) }); - return result; - } else { - const result = writeFn(ctx, .{ .temporary = bun.ByteList.init(buf[0..copied.written]) }); - return result; - } - } - - { - var allocated = strings.toUTF8Alloc(bun.default_allocator, str) catch return .{ .err = JSC.Node.Syscall.Error.oom }; - if (input.isDone()) { - return writeFn(ctx, .{ .owned_and_done = bun.ByteList.init(allocated) }); - } else { - return writeFn(ctx, .{ .owned = bun.ByteList.init(allocated) }); - } - } - } - }; - - pub const VTable = struct { - pub const WriteUTF16Fn = fn (this: *anyopaque, data: StreamResult) StreamResult.Writable; - pub const WriteUTF8Fn = fn (this: *anyopaque, data: StreamResult) StreamResult.Writable; - pub const WriteLatin1Fn = fn (this: *anyopaque, data: StreamResult) StreamResult.Writable; - pub const EndFn = fn (this: *anyopaque, err: ?JSC.Node.Syscall.Error) JSC.Node.Maybe(void); - pub const ConnectFn = fn (this: *anyopaque, signal: Signal) JSC.Node.Maybe(void); - - connect: ConnectFn, - write: WriteUTF8Fn, - writeLatin1: WriteLatin1Fn, - writeUTF16: WriteUTF16Fn, - end: EndFn, - - pub fn wrap( - comptime Wrapped: type, - ) VTable { - const Functions = struct { - pub fn onWrite(this: *anyopaque, data: StreamResult) StreamResult.Writable { - return Wrapped.write(@ptrCast(*Wrapped, @alignCast(std.meta.alignment(Wrapped), this)), data); - } - pub fn onConnect(this: *anyopaque, signal: Signal) JSC.Node.Maybe(void) { - return Wrapped.connect(@ptrCast(*Wrapped, @alignCast(std.meta.alignment(Wrapped), this)), signal); - } - pub fn onWriteLatin1(this: *anyopaque, data: StreamResult) StreamResult.Writable { - return Wrapped.writeLatin1(@ptrCast(*Wrapped, @alignCast(std.meta.alignment(Wrapped), this)), data); - } - pub fn onWriteUTF16(this: *anyopaque, data: StreamResult) StreamResult.Writable { - return Wrapped.writeUTF16(@ptrCast(*Wrapped, @alignCast(std.meta.alignment(Wrapped), this)), data); - } - pub fn onEnd(this: *anyopaque, err: ?JSC.Node.Syscall.Error) JSC.Node.Maybe(void) { - return Wrapped.end(@ptrCast(*Wrapped, @alignCast(std.meta.alignment(Wrapped), this)), err); - } - }; - - return VTable{ - .write = Functions.onWrite, - .writeLatin1 = Functions.onWriteLatin1, - .writeUTF16 = Functions.onWriteUTF16, - .end = Functions.onEnd, - .connect = Functions.onConnect, - }; - } - }; - - pub fn end(this: *Sink, err: ?JSC.Node.Syscall.Error) JSC.Node.Maybe(void) { - if (this.status == .closed) { - return .{ .result = {} }; - } - - this.status = .closed; - return this.vtable.end(this.ptr, err); - } - - pub fn writeLatin1(this: *Sink, data: StreamResult) StreamResult.Writable { - if (this.status == .closed) { - return .{ .done = {} }; - } - - const res = this.vtable.writeLatin1(this.ptr, data); - this.status = if ((res.isDone()) or this.status == .closed) - Status.closed - else - Status.ready; - this.used = true; - return res; - } - - pub fn writeBytes(this: *Sink, data: StreamResult) StreamResult.Writable { - if (this.status == .closed) { - return .{ .done = {} }; - } - - const res = this.vtable.write(this.ptr, data); - this.status = if ((res.isDone()) or this.status == .closed) - Status.closed - else - Status.ready; - this.used = true; - return res; - } - - pub fn writeUTF16(this: *Sink, data: StreamResult) StreamResult.Writable { - if (this.status == .closed) { - return .{ .done = {} }; - } - - const res = this.vtable.writeUTF16(this.ptr, data); - this.status = if ((res.isDone()) or this.status == .closed) - Status.closed - else - Status.ready; - this.used = true; - return res; - } - - pub fn write(this: *Sink, data: Data) StreamResult.Writable { - switch (data) { - .utf16 => |str| { - return this.writeUTF16(str); - }, - .latin1 => |str| { - return this.writeLatin1(str); - }, - .bytes => |bytes| { - return this.writeBytes(bytes); - }, - } - } -}; - -pub const ArrayBufferSink = struct { - bytes: bun.ByteList, - allocator: std.mem.Allocator, - done: bool = false, - signal: Signal = .{}, - next: ?Sink = null, - streaming: bool = false, - as_uint8array: bool = false, - - pub fn connect(this: *ArrayBufferSink, signal: Signal) void { - std.debug.assert(this.reader == null); - this.signal = signal; - } - - pub fn start(this: *ArrayBufferSink, stream_start: StreamStart) JSC.Node.Maybe(void) { - var list = this.bytes.listManaged(this.allocator); - list.clearAndFree(); - - switch (stream_start) { - .ArrayBufferSink => |config| { - if (config.chunk_size > 0) { - list.ensureTotalCapacityPrecise(config.chunk_size) catch return .{ .err = JSC.Node.Syscall.Error.oom }; - this.bytes.update(list); - } - - this.as_uint8array = config.as_uint8array; - this.streaming = config.stream; - }, - else => {}, - } - - this.done = false; - - this.signal.start(); - return .{ .result = {} }; - } - - pub fn drain(_: *ArrayBufferSink) JSC.Node.Maybe(void) { - return .{ .result = {} }; - } - - pub fn drainFromJS(this: *ArrayBufferSink, globalThis: *JSGlobalObject) JSC.Node.Maybe(JSValue) { - if (this.streaming) { - const value: JSValue = switch (this.as_uint8array) { - true => JSC.ArrayBuffer.create(globalThis, this.bytes.slice(), .Uint8Array), - false => JSC.ArrayBuffer.create(globalThis, this.bytes.slice(), .ArrayBuffer), - }; - this.bytes.len = 0; - return .{ .result = value }; - } - - return .{ .result = JSValue.jsUndefined() }; - } - - pub fn finalize(this: *ArrayBufferSink) void { - if (this.bytes.len > 0) { - this.bytes.listManaged(this.allocator).deinit(); - this.bytes = bun.ByteList.init(""); - this.done = true; - } - } - - pub fn init(allocator: std.mem.Allocator, next: ?Sink) !*ArrayBufferSink { - var this = try allocator.create(ArrayBufferSink); - this.* = ArrayBufferSink{ - .bytes = bun.ByteList.init(&.{}), - .allocator = allocator, - .next = next, - }; - return this; - } - - pub fn construct( - this: *ArrayBufferSink, - allocator: std.mem.Allocator, - ) void { - this.* = ArrayBufferSink{ - .bytes = bun.ByteList.init(&.{}), - .allocator = allocator, - .next = null, - }; - } - - pub fn write(this: *@This(), data: StreamResult) StreamResult.Writable { - if (this.next) |*next| { - return next.writeBytes(data); - } - - const len = this.bytes.write(this.allocator, data.slice()) catch { - return .{ .err = JSC.Node.Syscall.Error.oom }; - }; - this.signal.ready(null, null); - return .{ .owned = len }; - } - pub const writeBytes = write; - pub fn writeLatin1(this: *@This(), data: StreamResult) StreamResult.Writable { - if (this.next) |*next| { - return next.writeLatin1(data); - } - const len = this.bytes.writeLatin1(this.allocator, data.slice()) catch { - return .{ .err = JSC.Node.Syscall.Error.oom }; - }; - this.signal.ready(null, null); - return .{ .owned = len }; - } - pub fn writeUTF16(this: *@This(), data: StreamResult) StreamResult.Writable { - if (this.next) |*next| { - return next.writeUTF16(data); - } - const len = this.bytes.writeUTF16(this.allocator, @ptrCast([*]const u16, @alignCast(@alignOf(u16), data.slice().ptr))[0..std.mem.bytesAsSlice(u16, data.slice()).len]) catch { - return .{ .err = JSC.Node.Syscall.Error.oom }; - }; - this.signal.ready(null, null); - return .{ .owned = len }; - } - - pub fn end(this: *ArrayBufferSink, err: ?JSC.Node.Syscall.Error) JSC.Node.Maybe(void) { - if (this.next) |*next| { - return next.end(err); - } - this.signal.close(err); - return .{ .result = {} }; - } - - pub fn toJS(this: *ArrayBufferSink, globalThis: *JSGlobalObject, as_uint8array: bool) JSValue { - if (this.streaming) { - const value: JSValue = switch (as_uint8array) { - true => JSC.ArrayBuffer.create(globalThis, this.bytes.slice(), .Uint8Array), - false => JSC.ArrayBuffer.create(globalThis, this.bytes.slice(), .ArrayBuffer), - }; - this.bytes.len = 0; - return value; - } - - var list = this.bytes.listManaged(this.allocator); - this.bytes = bun.ByteList.init(""); - return ArrayBuffer.fromBytes( - list.toOwnedSlice(), - if (as_uint8array) - .Uint8Array - else - .ArrayBuffer, - ).toJS(globalThis, null); - } - - pub fn endFromJS(this: *ArrayBufferSink, _: *JSGlobalObject) JSC.Node.Maybe(ArrayBuffer) { - if (this.done) { - return .{ .result = ArrayBuffer.fromBytes(&[_]u8{}, .ArrayBuffer) }; - } - - std.debug.assert(this.next == null); - var list = this.bytes.listManaged(this.allocator); - this.bytes = bun.ByteList.init(""); - this.done = true; - this.signal.close(null); - return .{ .result = ArrayBuffer.fromBytes( - list.toOwnedSlice(), - if (this.as_uint8array) - .Uint8Array - else - .ArrayBuffer, - ) }; - } - - pub fn sink(this: *ArrayBufferSink) Sink { - return Sink.init(this); - } - - pub const JSSink = NewJSSink(@This(), "ArrayBufferSink"); -}; - -pub fn NewJSSink(comptime SinkType: type, comptime name_: []const u8) type { - return struct { - sink: SinkType, - - const ThisSink = @This(); - - pub const shim = JSC.Shimmer("", std.mem.span(name_), @This()); - pub const name = std.fmt.comptimePrint("{s}", .{std.mem.span(name_)}); - - pub fn createObject(globalThis: *JSGlobalObject, object: *anyopaque) callconv(.C) JSValue { - JSC.markBinding(); - - return shim.cppFn("createObject", .{ globalThis, object }); - } - - pub fn fromJS(globalThis: *JSGlobalObject, value: JSValue) ?*anyopaque { - JSC.markBinding(); - - return shim.cppFn("fromJS", .{ globalThis, value }); - } - - pub fn construct(globalThis: *JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSValue { - JSC.markBinding(); - var allocator = globalThis.bunVM().allocator; - var this = allocator.create(ThisSink) catch { - globalThis.vm().throwError(globalThis, JSC.Node.Syscall.Error.oom.toJSC( - globalThis, - )); - return JSC.JSValue.jsUndefined(); - }; - this.sink.construct(allocator); - return createObject(globalThis, this); - } - - pub fn finalize(ptr: *anyopaque) callconv(.C) void { - var this = @ptrCast(*ThisSink, @alignCast(std.meta.alignment(ThisSink), ptr)); - - this.sink.finalize(); - } - - pub fn write(globalThis: *JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue { - JSC.markBinding(); - var this = @ptrCast(*ThisSink, @alignCast(std.meta.alignment(ThisSink), fromJS(globalThis, callframe.this()) orelse { - const err = JSC.toTypeError(JSC.Node.ErrorCode.ERR_INVALID_THIS, "Expected Sink", .{}, globalThis); - globalThis.vm().throwError(globalThis, err); - return JSC.JSValue.jsUndefined(); - })); - - if (comptime @hasDecl(SinkType, "getPendingError")) { - if (this.sink.getPendingError()) |err| { - globalThis.vm().throwError(globalThis, err); - return JSC.JSValue.jsUndefined(); - } - } - - const args = callframe.arguments(); - if (args.len == 0 or args[0].isEmptyOrUndefinedOrNull() or args[0].isNumber()) { - const err = JSC.toTypeError( - if (args.len == 0) JSC.Node.ErrorCode.ERR_MISSING_ARGS else JSC.Node.ErrorCode.ERR_INVALID_ARG_TYPE, - "write() expects a string, ArrayBufferView, or ArrayBuffer", - .{}, - globalThis, - ); - globalThis.vm().throwError(globalThis, err); - return JSC.JSValue.jsUndefined(); - } - - const arg = args[0]; - if (arg.asArrayBuffer(globalThis)) |buffer| { - const slice = buffer.slice(); - if (slice.len == 0) { - return JSC.JSValue.jsNumber(0); - } - - return this.sink.writeBytes(.{ .temporary = bun.ByteList.init(slice) }).toJS(globalThis); - } - - const str = arg.getZigString(globalThis); - if (str.len == 0) { - return JSC.JSValue.jsNumber(0); - } - - if (str.is16Bit()) { - return this.sink.writeUTF16(.{ .temporary = bun.ByteList.init(std.mem.sliceAsBytes(str.utf16SliceAligned())) }).toJS(globalThis); - } - - return this.sink.writeLatin1(.{ .temporary = bun.ByteList.init(str.slice()) }).toJS(globalThis); - } - - pub fn writeString(globalThis: *JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue { - JSC.markBinding(); - - var this = @ptrCast(*ThisSink, @alignCast(std.meta.alignment(ThisSink), fromJS(globalThis, callframe.this()) orelse { - const err = JSC.toTypeError(JSC.Node.ErrorCode.ERR_INVALID_THIS, "Expected Sink", .{}, globalThis); - globalThis.vm().throwError(globalThis, err); - return JSC.JSValue.jsUndefined(); - })); - - if (comptime @hasDecl(SinkType, "getPendingError")) { - if (this.sink.getPendingError()) |err| { - globalThis.vm().throwError(globalThis, err); - return JSC.JSValue.jsUndefined(); - } - } - - const args = callframe.arguments(); - if (args.len == 0 or args[0].isEmptyOrUndefinedOrNull() or args[0].isNumber()) { - const err = JSC.toTypeError( - if (args.len == 0) JSC.Node.ErrorCode.ERR_MISSING_ARGS else JSC.Node.ErrorCode.ERR_INVALID_ARG_TYPE, - "write() expects a string, ArrayBufferView, or ArrayBuffer", - .{}, - globalThis, - ); - globalThis.vm().throwError(globalThis, err); - return JSC.JSValue.jsUndefined(); - } - - const arg = args[0]; - - const str = arg.getZigString(globalThis); - if (str.len == 0) { - return JSC.JSValue.jsNumber(0); - } - - if (str.is16Bit()) { - return this.sink.writeUTF16(.{ .temporary = str.utf16SliceAligned() }).toJS(globalThis); - } - - return this.sink.writeLatin1(.{ .temporary = str.slice() }).toJS(globalThis); - } - - pub fn close(globalThis: *JSGlobalObject, sink_ptr: ?*anyopaque) callconv(.C) JSValue { - JSC.markBinding(); - var this = @ptrCast(*ThisSink, @alignCast(std.meta.alignment(ThisSink), sink_ptr) orelse { - const err = JSC.toTypeError(JSC.Node.ErrorCode.ERR_INVALID_THIS, "Expected Sink", .{}, globalThis); - globalThis.vm().throwError(globalThis, err); - return JSC.JSValue.jsUndefined(); - }); - - if (comptime @hasDecl(SinkType, "getPendingError")) { - if (this.sink.getPendingError()) |err| { - globalThis.vm().throwError(globalThis, err); - return JSC.JSValue.jsUndefined(); - } - } - - return this.sink.end(null).toJS(globalThis); - } - - pub fn drain(globalThis: *JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue { - JSC.markBinding(); - - var this = @ptrCast(*ThisSink, @alignCast(std.meta.alignment(ThisSink), fromJS(globalThis, callframe.this()) orelse { - const err = JSC.toTypeError(JSC.Node.ErrorCode.ERR_INVALID_THIS, "Expected Sink", .{}, globalThis); - globalThis.vm().throwError(globalThis, err); - return JSC.JSValue.jsUndefined(); - })); - - if (comptime @hasDecl(SinkType, "getPendingError")) { - if (this.sink.getPendingError()) |err| { - globalThis.vm().throwError(globalThis, err); - return JSC.JSValue.jsUndefined(); - } - } - - if (comptime @hasDecl(SinkType, "drainFromJS")) { - return this.sink.drainFromJS(globalThis).result; - } - - return this.sink.drain().toJS(globalThis); - } - - pub fn start(globalThis: *JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue { - JSC.markBinding(); - - var this = @ptrCast(*ThisSink, @alignCast(std.meta.alignment(ThisSink), fromJS(globalThis, callframe.this()) orelse { - const err = JSC.toTypeError(JSC.Node.ErrorCode.ERR_INVALID_THIS, "Expected Sink", .{}, globalThis); - globalThis.vm().throwError(globalThis, err); - return JSC.JSValue.jsUndefined(); - })); - - if (comptime @hasDecl(SinkType, "getPendingError")) { - if (this.sink.getPendingError()) |err| { - globalThis.vm().throwError(globalThis, err); - return JSC.JSValue.jsUndefined(); - } - } - - if (comptime @hasField(StreamStart, name_)) { - return this.sink.start( - if (callframe.argumentsCount() > 0) - StreamStart.fromJSWithTag( - globalThis, - callframe.argument(0), - comptime @field(StreamStart, name_), - ) - else - StreamStart{ .empty = {} }, - ).toJS(globalThis); - } - - return this.sink.start( - if (callframe.argumentsCount() > 0) - StreamStart.fromJS(globalThis, callframe.argument(0)) - else - StreamStart{ .empty = {} }, - ).toJS(globalThis); - } - - pub fn end(globalThis: *JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue { - JSC.markBinding(); - - var this = @ptrCast(*ThisSink, @alignCast(std.meta.alignment(ThisSink), fromJS(globalThis, callframe.this()) orelse { - const err = JSC.toTypeError(JSC.Node.ErrorCode.ERR_INVALID_THIS, "Expected Sink", .{}, globalThis); - globalThis.vm().throwError(globalThis, err); - return JSC.JSValue.jsUndefined(); - })); - - if (comptime @hasDecl(SinkType, "getPendingError")) { - if (this.sink.getPendingError()) |err| { - globalThis.vm().throwError(globalThis, err); - return JSC.JSValue.jsUndefined(); - } - } - - return this.sink.endFromJS(globalThis).toJS(globalThis); - } - - pub const Export = shim.exportFunctions(.{ - .@"finalize" = finalize, - .@"write" = write, - .@"close" = close, - .@"drain" = drain, - .@"start" = start, - .@"end" = end, - .@"construct" = construct, - }); - - comptime { - if (!JSC.is_bindgen) { - @export(finalize, .{ .name = Export[0].symbol_name }); - @export(write, .{ .name = Export[1].symbol_name }); - @export(close, .{ .name = Export[2].symbol_name }); - @export(drain, .{ .name = Export[3].symbol_name }); - @export(start, .{ .name = Export[4].symbol_name }); - @export(end, .{ .name = Export[5].symbol_name }); - @export(construct, .{ .name = Export[6].symbol_name }); - } - } - - pub const Extern = [_][]const u8{ "createObject", "fromJS" }; - }; -} - -pub fn WritableStreamSink( - comptime Context: type, - comptime onStart: ?fn (this: Context) void, - comptime onWrite: fn (this: Context, bytes: []const u8) JSC.Maybe(Blob.SizeType), - comptime onAbort: ?fn (this: Context) void, - comptime onClose: ?fn (this: Context) void, - comptime deinit: ?fn (this: Context) void, -) type { - return struct { - context: Context, - closed: bool = false, - deinited: bool = false, - pending_err: ?JSC.Node.Syscall.Error = null, - aborted: bool = false, - - abort_signaler: ?*anyopaque = null, - onAbortCallback: ?fn (?*anyopaque) void = null, - - close_signaler: ?*anyopaque = null, - onCloseCallback: ?fn (?*anyopaque) void = null, - - pub const This = @This(); - - pub fn write(this: *This, bytes: []const u8) JSC.Maybe(Blob.SizeType) { - if (this.pending_err) |err| { - this.pending_err = null; - return .{ .err = err }; - } - - if (this.closed or this.aborted or this.deinited) { - return .{ .result = 0 }; - } - return onWrite(&this.context, bytes); - } - - pub fn start(this: *This) StreamStart { - return onStart(&this.context); - } - - pub fn abort(this: *This) void { - if (this.closed or this.deinited or this.aborted) { - return; - } - - this.aborted = true; - onAbort(&this.context); - } - - pub fn didAbort(this: *This) void { - if (this.closed or this.deinited or this.aborted) { - return; - } - this.aborted = true; - - if (this.onAbortCallback) |cb| { - this.onAbortCallback = null; - cb(this.abort_signaler); - } - } - - pub fn didClose(this: *This) void { - if (this.closed or this.deinited or this.aborted) { - return; - } - this.closed = true; - - if (this.onCloseCallback) |cb| { - this.onCloseCallback = null; - cb(this.close_signaler); - } - } - - pub fn close(this: *This) void { - if (this.closed or this.deinited or this.aborted) { - return; - } - - this.closed = true; - onClose(this.context); - } - - pub fn deinit(this: *This) void { - if (this.deinited) { - return; - } - this.deinited = true; - deinit(this.context); - } - - pub fn getError(this: *This) ?JSC.Node.Syscall.Error { - if (this.pending_err) |err| { - this.pending_err = null; - return err; - } - - return null; - } - }; -} - -pub fn HTTPServerWritable(comptime ssl: bool) type { - return struct { - pub const UWSResponse = uws.NewApp(ssl).Response; - res: *UWSResponse, - pending_chunk: []const u8 = "", - is_listening_for_abort: bool = false, - wrote: Blob.SizeType = 0, - callback: anyframe->JSC.Maybe(Blob.SizeType) = undefined, - writable: Writable, - - pub fn onWritable(this: *@This(), available: c_ulong, _: *UWSResponse) callconv(.C) bool { - const to_write = @minimum(@truncate(Blob.SizeType, available), @truncate(Blob.SizeType, this.pending_chunk.len)); - if (!this.res.write(this.pending_chunk[0..to_write])) { - return true; - } - - this.pending_chunk = this.pending_chunk[to_write..]; - this.wrote += to_write; - if (this.pending_chunk.len > 0) { - this.res.onWritable(*@This(), onWritable, this); - return true; - } - - var callback = this.callback; - this.callback = undefined; - // TODO: clarify what the boolean means - resume callback; - bun.default_allocator.destroy(callback.*); - return false; - } - - pub fn onStart(this: *@This()) void { - if (this.res.hasResponded()) { - this.writable.didClose(); - } - } - pub fn onWrite(this: *@This(), bytes: []const u8) JSC.Maybe(Blob.SizeType) { - if (this.writable.aborted) { - return .{ .result = 0 }; - } - - if (this.pending_chunk.len > 0) { - return JSC.Maybe(Blob.SizeType).retry; - } - - if (this.res.write(bytes)) { - return .{ .result = @truncate(Blob.SizeType, bytes.len) }; - } - - this.pending_chunk = bytes; - this.writable.pending_err = null; - suspend { - if (!this.is_listening_for_abort) { - this.is_listening_for_abort = true; - this.res.onAborted(*@This(), onAborted); - } - - this.res.onWritable(*@This(), onWritable, this); - var frame = bun.default_allocator.create(@TypeOf(@Frame(onWrite))) catch unreachable; - this.callback = frame; - frame.* = @frame().*; - } - const wrote = this.wrote; - this.wrote = 0; - if (this.writable.pending_err) |err| { - this.writable.pending_err = null; - return .{ .err = err }; - } - return .{ .result = wrote }; - } - - // client-initiated - pub fn onAborted(this: *@This(), _: *UWSResponse) void { - this.writable.didAbort(); - } - // writer-initiated - pub fn onAbort(this: *@This()) void { - this.res.end("", true); - } - pub fn onClose(this: *@This()) void { - this.res.end("", false); - } - pub fn deinit(_: *@This()) void {} - - pub const Writable = WritableStreamSink(@This(), onStart, onWrite, onAbort, onClose, deinit); - }; -} -pub const HTTPSWriter = HTTPServerWritable(true); -pub const HTTPWriter = HTTPServerWritable(false); - -pub fn ReadableStreamSource( - comptime Context: type, - comptime name_: []const u8, - comptime onStart: anytype, - comptime onPull: anytype, - comptime onCancel: fn (this: *Context) void, - comptime deinit: fn (this: *Context) void, -) type { - return struct { - context: Context, - cancelled: bool = false, - deinited: bool = false, - pending_err: ?JSC.Node.Syscall.Error = null, - close_handler: ?fn (*anyopaque) void = null, - close_ctx: ?*anyopaque = null, - close_jsvalue: JSValue = JSValue.zero, - globalThis: *JSGlobalObject = undefined, - - const This = @This(); - const ReadableStreamSourceType = @This(); - - pub fn pull(this: *This, buf: []u8) StreamResult { - return onPull(&this.context, buf, JSValue.zero); - } - - pub fn start( - this: *This, - ) StreamStart { - return onStart(&this.context); - } - - pub fn pullFromJS(this: *This, buf: []u8, view: JSValue) StreamResult { - return onPull(&this.context, buf, view); - } - - pub fn startFromJS(this: *This) StreamStart { - return onStart(&this.context); - } - - pub fn cancel(this: *This) void { - if (this.cancelled or this.deinited) { - return; - } - - this.cancelled = true; - onCancel(&this.context); - } - - pub fn onClose(this: *This) void { - if (this.cancelled or this.deinited) { - return; - } - - if (this.close_handler) |close| { - this.close_handler = null; - close(this.close_ctx); - } - } - - pub fn deinit(this: *This) void { - if (this.deinited) { - return; - } - this.deinited = true; - deinit(&this.context); - } - - pub fn getError(this: *This) ?JSC.Node.Syscall.Error { - if (this.pending_err) |err| { - this.pending_err = null; - return err; - } - - return null; - } - - pub fn toJS(this: *ReadableStreamSourceType, globalThis: *JSGlobalObject) JSC.JSValue { - return ReadableStream.fromNative(globalThis, Context.tag, this); - } - - pub const JSReadableStreamSource = struct { - pub const shim = JSC.Shimmer(std.mem.span(name_), "JSReadableStreamSource", @This()); - pub const name = std.fmt.comptimePrint("{s}_JSReadableStreamSource", .{std.mem.span(name_)}); - - pub fn pull(globalThis: *JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSC.JSValue { - var this = callFrame.argument(0).asPtr(ReadableStreamSourceType); - const view = callFrame.argument(1); - view.ensureStillAlive(); - var buffer = view.asArrayBuffer(globalThis) orelse return JSC.JSValue.jsUndefined(); - return processResult( - globalThis, - callFrame, - this.pullFromJS(buffer.slice(), view), - ); - } - pub fn start(globalThis: *JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSC.JSValue { - var this = callFrame.argument(0).asPtr(ReadableStreamSourceType); - switch (this.startFromJS()) { - .empty => return JSValue.jsNumber(0), - .ready => return JSValue.jsNumber(16384), - .chunk_size => |size| return JSValue.jsNumber(size), - .err => |err| { - globalThis.vm().throwError(globalThis, err.toJSC(globalThis)); - return JSC.JSValue.jsUndefined(); - }, - else => unreachable, - } - } - - pub fn processResult(globalThis: *JSGlobalObject, callFrame: *JSC.CallFrame, result: StreamResult) JSC.JSValue { - switch (result) { - .err => |err| { - globalThis.vm().throwError(globalThis, err.toJSC(globalThis)); - return JSValue.jsUndefined(); - }, - .temporary_and_done, .owned_and_done, .into_array_and_done => { - JSC.C.JSObjectSetPropertyAtIndex(globalThis.ref(), callFrame.argument(2).asObjectRef(), 0, JSValue.jsBoolean(true).asObjectRef(), null); - return result.toJS(globalThis); - }, - else => return result.toJS(globalThis), - } - } - pub fn cancel(_: *JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSC.JSValue { - var this = callFrame.argument(0).asPtr(ReadableStreamSourceType); - this.cancel(); - return JSC.JSValue.jsUndefined(); - } - pub fn setClose(globalThis: *JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSC.JSValue { - var this = callFrame.argument(0).asPtr(ReadableStreamSourceType); - this.close_ctx = this; - this.close_handler = JSReadableStreamSource.onClose; - this.globalThis = globalThis; - this.close_jsvalue = callFrame.argument(1); - return JSC.JSValue.jsUndefined(); - } - - fn onClose(ptr: *anyopaque) void { - var this = bun.cast(*ReadableStreamSourceType, ptr); - _ = this.close_jsvalue.call(this.globalThis, &.{}); - // this.closer - } - - pub fn deinit(_: *JSGlobalObject, callFrame: *JSC.CallFrame) callconv(.C) JSC.JSValue { - var this = callFrame.argument(0).asPtr(ReadableStreamSourceType); - this.deinit(); - return JSValue.jsUndefined(); - } - - pub fn load(globalThis: *JSGlobalObject) callconv(.C) JSC.JSValue { - if (comptime JSC.is_bindgen) unreachable; - if (comptime Environment.allow_assert) { - // this should be cached per globals object - const OnlyOnce = struct { - pub threadlocal var last_globals: ?*JSGlobalObject = null; - }; - if (OnlyOnce.last_globals) |last_globals| { - std.debug.assert(last_globals != globalThis); - } - OnlyOnce.last_globals = globalThis; - } - return JSC.JSArray.from(globalThis, &.{ - JSC.NewFunction(globalThis, null, 1, JSReadableStreamSource.pull), - JSC.NewFunction(globalThis, null, 1, JSReadableStreamSource.start), - JSC.NewFunction(globalThis, null, 1, JSReadableStreamSource.cancel), - JSC.NewFunction(globalThis, null, 1, JSReadableStreamSource.setClose), - JSC.NewFunction(globalThis, null, 1, JSReadableStreamSource.deinit), - }); - } - - pub const Export = shim.exportFunctions(.{ - .@"load" = load, - }); - - comptime { - if (!JSC.is_bindgen) { - @export(load, .{ .name = Export[0].symbol_name }); - _ = JSReadableStreamSource.pull; - _ = JSReadableStreamSource.start; - _ = JSReadableStreamSource.cancel; - _ = JSReadableStreamSource.setClose; - _ = JSReadableStreamSource.load; - } - } - }; - }; -} - -pub const ByteBlobLoader = struct { - offset: Blob.SizeType = 0, - store: *Blob.Store, - chunk_size: Blob.SizeType = 1024 * 1024 * 2, - remain: Blob.SizeType = 1024 * 1024 * 2, - done: bool = false, - - pub const tag = ReadableStream.Tag.Blob; - - pub fn setup( - this: *ByteBlobLoader, - blob: *const Blob, - user_chunk_size: Blob.SizeType, - ) void { - blob.store.?.ref(); - var blobe = blob.*; - blobe.resolveSize(); - this.* = ByteBlobLoader{ - .offset = blobe.offset, - .store = blobe.store.?, - .chunk_size = if (user_chunk_size > 0) @minimum(user_chunk_size, blobe.size) else @minimum(1024 * 1024 * 2, blobe.size), - .remain = blobe.size, - .done = false, - }; - } - - pub fn onStart(this: *ByteBlobLoader) StreamStart { - return .{ .chunk_size = this.chunk_size }; - } - - pub fn onPull(this: *ByteBlobLoader, buffer: []u8, array: JSC.JSValue) StreamResult { - array.ensureStillAlive(); - defer array.ensureStillAlive(); - if (this.done) { - return .{ .done = {} }; - } - - var temporary = this.store.sharedView(); - temporary = temporary[this.offset..]; - - temporary = temporary[0..@minimum(buffer.len, @minimum(temporary.len, this.remain))]; - if (temporary.len == 0) { - this.store.deref(); - this.done = true; - return .{ .done = {} }; - } - - const copied = @intCast(Blob.SizeType, temporary.len); - - this.remain -|= copied; - this.offset +|= copied; - @memcpy(buffer.ptr, temporary.ptr, temporary.len); - if (this.remain == 0) { - return .{ .into_array_and_done = .{ .value = array, .len = copied } }; - } - - return .{ .into_array = .{ .value = array, .len = copied } }; - } - - pub fn onCancel(_: *ByteBlobLoader) void {} - - pub fn deinit(this: *ByteBlobLoader) void { - if (!this.done) { - this.done = true; - this.store.deref(); - } - - bun.default_allocator.destroy(this); - } - - pub const Source = ReadableStreamSource(@This(), "ByteBlob", onStart, onPull, onCancel, deinit); -}; - -pub fn RequestBodyStreamer( - comptime is_ssl: bool, -) type { - return struct { - response: *uws.NewApp(is_ssl).Response, - - pub const tag = if (is_ssl) - ReadableStream.Tag.HTTPRequest - else if (is_ssl) - ReadableStream.Tag.HTTPSRequest; - - pub fn onStart(this: *ByteBlobLoader) StreamStart { - return .{ .chunk_size = this.chunk_size }; - } - - pub fn onPull(this: *ByteBlobLoader, buffer: []u8, array: JSC.JSValue) StreamResult { - array.ensureStillAlive(); - defer array.ensureStillAlive(); - if (this.done) { - return .{ .done = {} }; - } - - var temporary = this.store.sharedView(); - temporary = temporary[this.offset..]; - - temporary = temporary[0..@minimum(buffer.len, @minimum(temporary.len, this.remain))]; - if (temporary.len == 0) { - this.store.deref(); - this.done = true; - return .{ .done = {} }; - } - - const copied = @intCast(Blob.SizeType, temporary.len); - - this.remain -|= copied; - this.offset +|= copied; - @memcpy(buffer.ptr, temporary.ptr, temporary.len); - if (this.remain == 0) { - return .{ .into_array_and_done = .{ .value = array, .len = copied } }; - } - - return .{ .into_array = .{ .value = array, .len = copied } }; - } - - pub fn onCancel(_: *ByteBlobLoader) void {} - - pub fn deinit(this: *ByteBlobLoader) void { - if (!this.done) { - this.done = true; - this.store.deref(); - } - - bun.default_allocator.destroy(this); - } - - pub const label = if (is_ssl) "HTTPSRequestBodyStreamer" else "HTTPRequestBodyStreamer"; - pub const Source = ReadableStreamSource(@This(), label, onStart, onPull, onCancel, deinit); - }; -} - -pub const FileBlobLoader = struct { - buf: []u8 = &[_]u8{}, - protected_view: JSC.JSValue = JSC.JSValue.zero, - fd: JSC.Node.FileDescriptor = 0, - auto_close: bool = false, - loop: *JSC.EventLoop = undefined, - mode: JSC.Node.Mode = 0, - store: *Blob.Store, - total_read: Blob.SizeType = 0, - finalized: bool = false, - callback: anyframe = undefined, - pending: StreamResult.Pending = StreamResult.Pending{ - .frame = undefined, - .used = false, - .result = .{ .done = {} }, - }, - cancelled: bool = false, - user_chunk_size: Blob.SizeType = 0, - scheduled_count: u32 = 0, - concurrent: Concurrent = Concurrent{}, - input_tag: StreamResult.Tag = StreamResult.Tag.done, - - const FileReader = @This(); - - const run_on_different_thread_size = bun.huge_allocator_threshold; - - pub const tag = ReadableStream.Tag.File; - - pub fn setup(this: *FileBlobLoader, store: *Blob.Store, chunk_size: Blob.SizeType) void { - store.ref(); - this.* = .{ - .loop = JSC.VirtualMachine.vm.eventLoop(), - .auto_close = store.data.file.pathlike == .path, - .store = store, - .user_chunk_size = chunk_size, - }; - } - - pub fn watch(this: *FileReader) void { - _ = JSC.VirtualMachine.vm.poller.watch(this.fd, .read, this, callback); - this.scheduled_count += 1; - } - - const Concurrent = struct { - read: Blob.SizeType = 0, - task: NetworkThread.Task = .{ .callback = Concurrent.taskCallback }, - completion: AsyncIO.Completion = undefined, - read_frame: anyframe = undefined, - chunk_size: Blob.SizeType = 0, - main_thread_task: JSC.AnyTask = .{ .callback = onJSThread, .ctx = null }, - - pub fn taskCallback(task: *NetworkThread.Task) void { - var this = @fieldParentPtr(FileBlobLoader, "concurrent", @fieldParentPtr(Concurrent, "task", task)); - var frame = HTTPClient.getAllocator().create(@Frame(runAsync)) catch unreachable; - _ = @asyncCall(std.mem.asBytes(frame), undefined, runAsync, .{this}); - } - - pub fn onRead(this: *FileBlobLoader, completion: *HTTPClient.NetworkThread.Completion, result: AsyncIO.ReadError!usize) void { - this.concurrent.read = @truncate(Blob.SizeType, result catch |err| { - if (@hasField(HTTPClient.NetworkThread.Completion, "result")) { - this.pending.result = .{ - .err = JSC.Node.Syscall.Error{ - .errno = @intCast(JSC.Node.Syscall.Error.Int, -completion.result), - .syscall = .read, - }, - }; - } else { - this.pending.result = .{ - .err = JSC.Node.Syscall.Error{ - // this is too hacky - .errno = @truncate(JSC.Node.Syscall.Error.Int, @intCast(u16, @maximum(1, @errorToInt(err)))), - .syscall = .read, - }, - }; - } - this.concurrent.read = 0; - resume this.concurrent.read_frame; - return; - }); - - resume this.concurrent.read_frame; - } - - pub fn scheduleRead(this: *FileBlobLoader) void { - if (comptime Environment.isMac) { - var remaining = this.buf[this.concurrent.read..]; - - while (remaining.len > 0) { - const to_read = @minimum(@as(usize, this.concurrent.chunk_size), remaining.len); - switch (JSC.Node.Syscall.read(this.fd, remaining[0..to_read])) { - .err => |err| { - const retry = comptime if (Environment.isLinux) - std.os.E.WOULDBLOCK - else - std.os.E.AGAIN; - - switch (err.getErrno()) { - retry => break, - else => {}, - } - - this.pending.result = .{ .err = err }; - scheduleMainThreadTask(this); - return; - }, - .result => |result| { - this.concurrent.read += @intCast(Blob.SizeType, result); - remaining = remaining[result..]; - - if (result == 0) { - remaining.len = 0; - break; - } - }, - } - } - - if (remaining.len == 0) { - scheduleMainThreadTask(this); - return; - } - } - - AsyncIO.global.read( - *FileBlobLoader, - this, - onRead, - &this.concurrent.completion, - this.fd, - this.buf[this.concurrent.read..], - null, - ); - - suspend { - var _frame = @frame(); - var this_frame = HTTPClient.getAllocator().create(std.meta.Child(@TypeOf(_frame))) catch unreachable; - this_frame.* = _frame.*; - this.concurrent.read_frame = this_frame; - } - - scheduleMainThreadTask(this); - } - - pub fn onJSThread(task_ctx: *anyopaque) void { - var this: *FileBlobLoader = bun.cast(*FileBlobLoader, task_ctx); - const protected_view = this.protected_view; - defer protected_view.unprotect(); - this.protected_view = JSC.JSValue.zero; - - if (this.finalized and this.scheduled_count == 0) { - if (!this.pending.used) { - resume this.pending.frame; - } - this.scheduled_count -= 1; - - this.deinit(); - - return; - } - - if (!this.pending.used and this.pending.result == .err and this.concurrent.read == 0) { - resume this.pending.frame; - this.scheduled_count -= 1; - this.finalize(); - return; - } - - if (this.concurrent.read == 0) { - this.pending.result = .{ .done = {} }; - resume this.pending.frame; - this.scheduled_count -= 1; - this.finalize(); - return; - } - - this.pending.result = this.handleReadChunk(@as(usize, this.concurrent.read)); - resume this.pending.frame; - this.scheduled_count -= 1; - if (this.pending.result.isDone()) { - this.finalize(); - } - } - - pub fn scheduleMainThreadTask(this: *FileBlobLoader) void { - this.concurrent.main_thread_task.ctx = this; - this.loop.enqueueTaskConcurrent(JSC.Task.init(&this.concurrent.main_thread_task)); - } - - fn runAsync(this: *FileBlobLoader) void { - this.concurrent.read = 0; - - Concurrent.scheduleRead(this); - - suspend { - HTTPClient.getAllocator().destroy(@frame()); - } - } - }; - - pub fn scheduleAsync(this: *FileReader, chunk_size: Blob.SizeType) void { - this.scheduled_count += 1; - this.loop.virtual_machine.active_tasks +|= 1; - - NetworkThread.init() catch {}; - this.concurrent.chunk_size = chunk_size; - NetworkThread.global.pool.schedule(.{ .head = &this.concurrent.task, .tail = &this.concurrent.task, .len = 1 }); - } - - const default_fifo_chunk_size = 1024; - const default_file_chunk_size = 1024 * 1024 * 2; - pub fn onStart(this: *FileBlobLoader) StreamStart { - var file = &this.store.data.file; - var file_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; - var auto_close = this.auto_close; - defer this.auto_close = auto_close; - var fd = if (!auto_close) - file.pathlike.fd - else switch (JSC.Node.Syscall.open(file.pathlike.path.sliceZ(&file_buf), std.os.O.RDONLY | std.os.O.NONBLOCK | std.os.O.CLOEXEC, 0)) { - .result => |_fd| _fd, - .err => |err| { - this.deinit(); - return .{ .err = err.withPath(file.pathlike.path.slice()) }; - }, - }; - - if (!auto_close) { - // ensure we have non-blocking IO set - const flags = std.os.fcntl(fd, std.os.F.GETFL, 0) catch return .{ .err = JSC.Node.Syscall.Error.fromCode(std.os.E.BADF, .fcntl) }; - - // if we do not, clone the descriptor and set non-blocking - // it is important for us to clone it so we don't cause Weird Things to happen - if ((flags & std.os.O.NONBLOCK) == 0) { - auto_close = true; - fd = @intCast(@TypeOf(fd), std.os.fcntl(fd, std.os.F.DUPFD, 0) catch return .{ .err = JSC.Node.Syscall.Error.fromCode(std.os.E.BADF, .fcntl) }); - _ = std.os.fcntl(fd, std.os.F.SETFL, flags | std.os.O.NONBLOCK) catch return .{ .err = JSC.Node.Syscall.Error.fromCode(std.os.E.BADF, .fcntl) }; - } - } - - const stat: std.os.Stat = switch (JSC.Node.Syscall.fstat(fd)) { - .result => |result| result, - .err => |err| { - if (auto_close) { - _ = JSC.Node.Syscall.close(fd); - } - this.deinit(); - return .{ .err = err.withPath(file.pathlike.path.slice()) }; - }, - }; - - if (std.os.S.ISDIR(stat.mode)) { - const err = JSC.Node.Syscall.Error.fromCode(.ISDIR, .fstat); - if (auto_close) { - _ = JSC.Node.Syscall.close(fd); - } - this.deinit(); - return .{ .err = err }; - } - - if (std.os.S.ISSOCK(stat.mode)) { - const err = JSC.Node.Syscall.Error.fromCode(.INVAL, .fstat); - - if (auto_close) { - _ = JSC.Node.Syscall.close(fd); - } - this.deinit(); - return .{ .err = err }; - } - - file.seekable = std.os.S.ISREG(stat.mode); - file.mode = @intCast(JSC.Node.Mode, stat.mode); - this.mode = file.mode; - - if (file.seekable orelse false) - file.max_size = @intCast(Blob.SizeType, stat.size); - - if ((file.seekable orelse false) and file.max_size == 0) { - if (auto_close) { - _ = JSC.Node.Syscall.close(fd); - } - this.deinit(); - return .{ .empty = {} }; - } - - this.fd = fd; - this.auto_close = auto_close; - - const chunk_size = this.calculateChunkSize(std.math.maxInt(usize)); - return .{ .chunk_size = @truncate(Blob.SizeType, chunk_size) }; - } - - fn calculateChunkSize(this: *FileBlobLoader, available_to_read: usize) usize { - const file = &this.store.data.file; - - const chunk_size: usize = if (this.user_chunk_size > 0) - @as(usize, this.user_chunk_size) - else if (file.seekable orelse false) - @as(usize, default_file_chunk_size) - else - @as(usize, default_fifo_chunk_size); - - return if (file.max_size > 0) - if (available_to_read != std.math.maxInt(usize)) @minimum(chunk_size, available_to_read) else @minimum(@maximum(this.total_read, file.max_size) - this.total_read, chunk_size) - else - @minimum(available_to_read, chunk_size); - } - - pub fn onPullInto(this: *FileBlobLoader, buffer: []u8, view: JSC.JSValue) StreamResult { - const chunk_size = this.calculateChunkSize(std.math.maxInt(usize)); - this.input_tag = .into_array; - - switch (chunk_size) { - 0 => { - std.debug.assert(this.store.data.file.seekable orelse false); - this.finalize(); - return .{ .done = {} }; - }, - run_on_different_thread_size...std.math.maxInt(@TypeOf(chunk_size)) => { - this.protected_view = view; - this.protected_view.protect(); - // should never be reached - this.pending.result = .{ - .err = JSC.Node.Syscall.Error.todo, - }; - this.buf = buffer; - - this.scheduleAsync(@truncate(Blob.SizeType, chunk_size)); - - return .{ .pending = &this.pending }; - }, - else => {}, - } - - return this.read(buffer, view); - } - - fn maybeAutoClose(this: *FileBlobLoader) void { - if (this.auto_close) { - _ = JSC.Node.Syscall.close(this.fd); - this.auto_close = false; - } - } - - fn handleReadChunk(this: *FileBlobLoader, result: usize) StreamResult { - this.total_read += @intCast(Blob.SizeType, result); - const remaining: Blob.SizeType = if (this.store.data.file.seekable orelse false) - this.store.data.file.max_size -| this.total_read - else - @as(Blob.SizeType, std.math.maxInt(Blob.SizeType)); - - // this handles: - // - empty file - // - stream closed for some reason - if ((result == 0 and remaining == 0)) { - this.finalize(); - return .{ .done = {} }; - } - - const has_more = remaining > 0; - - if (!has_more) { - return .{ .into_array_and_done = .{ .len = @truncate(Blob.SizeType, result) } }; - } - - return .{ .into_array = .{ .len = @truncate(Blob.SizeType, result) } }; - } - - pub fn read( - this: *FileBlobLoader, - read_buf: []u8, - view: JSC.JSValue, - ) StreamResult { - const rc = - JSC.Node.Syscall.read(this.fd, read_buf); - - switch (rc) { - .err => |err| { - const retry = - std.os.E.AGAIN; - - switch (err.getErrno()) { - retry => { - this.protected_view = view; - this.protected_view.protect(); - this.buf = read_buf; - this.watch(); - return .{ - .pending = &this.pending, - }; - }, - else => {}, - } - const sys = if (this.store.data.file.pathlike == .path and this.store.data.file.pathlike.path.slice().len > 0) - err.withPath(this.store.data.file.pathlike.path.slice()) - else - err; - - this.finalize(); - return .{ .err = sys }; - }, - .result => |result| { - return this.handleReadChunk(result); - }, - } - } - - pub fn callback(task: ?*anyopaque, sizeOrOffset: i64, _: u16) void { - var this: *FileReader = bun.cast(*FileReader, task.?); - this.scheduled_count -= 1; - const protected_view = this.protected_view; - defer protected_view.unprotect(); - this.protected_view = JSValue.zero; - - var available_to_read: usize = std.math.maxInt(usize); - if (comptime Environment.isMac) { - if (std.os.S.ISREG(this.mode)) { - // Returns when the file pointer is not at the end of - // file. data contains the offset from current position - // to end of file, and may be negative. - available_to_read = @intCast(usize, @maximum(sizeOrOffset, 0)); - } else if (std.os.S.ISCHR(this.mode) or std.os.S.ISFIFO(this.mode)) { - available_to_read = @intCast(usize, @maximum(sizeOrOffset, 0)); - } - } - if (this.finalized and this.scheduled_count == 0) { - if (!this.pending.used) { - // should never be reached - this.pending.result = .{ - .err = JSC.Node.Syscall.Error.todo, - }; - resume this.pending.frame; - } - this.deinit(); - return; - } - if (this.cancelled) - return; - - if (this.buf.len == 0) { - return; - } else { - this.buf.len = @minimum(this.buf.len, available_to_read); - } - - this.pending.result = this.read(this.buf, this.protected_view); - resume this.pending.frame; - } - - pub fn finalize(this: *FileBlobLoader) void { - if (this.finalized) - return; - this.finalized = true; - - this.maybeAutoClose(); - - this.store.deref(); - } - - pub fn onCancel(this: *FileBlobLoader) void { - this.cancelled = true; - - this.deinit(); - } - - pub fn deinit(this: *FileBlobLoader) void { - this.finalize(); - if (this.scheduled_count == 0 and !this.pending.used) { - this.destroy(); - } - } - - pub fn destroy(this: *FileBlobLoader) void { - bun.default_allocator.destroy(this); - } - - pub const Source = ReadableStreamSource(@This(), "FileBlobLoader", onStart, onPullInto, onCancel, deinit); -}; - -// pub const HTTPRequest = RequestBodyStreamer(false); -// pub const HTTPSRequest = RequestBodyStreamer(true); -// pub fn ResponseBodyStreamer(comptime is_ssl: bool) type { -// return struct { -// const Streamer = @This(); -// pub fn onEnqueue(this: *Streamer, buffer: []u8, ): anytype, -// pub fn onEnqueueMany(this: *Streamer): anytype, -// pub fn onClose(this: *Streamer): anytype, -// pub fn onError(this: *Streamer): anytype, -// }; -// } |