mirror of
https://github.com/oven-sh/bun
synced 2026-02-15 05:12:29 +00:00
Fix cloning File with structuredClone (#11766)
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
This commit is contained in:
5
.github/workflows/build-windows.yml
vendored
5
.github/workflows/build-windows.yml
vendored
@@ -93,10 +93,7 @@ jobs:
|
||||
CCACHE_DIR: ccache
|
||||
run: |
|
||||
.\scripts\env.ps1 ${{ contains(inputs.tag, '-baseline') && '-Baseline' || '' }}
|
||||
Invoke-WebRequest -Uri "https://www.nasm.us/pub/nasm/releasebuilds/2.16.01/win64/nasm-2.16.01-win64.zip" -OutFile nasm.zip
|
||||
Expand-Archive nasm.zip (mkdir -Force "nasm")
|
||||
$Nasm = (Get-ChildItem "nasm")
|
||||
$env:Path += ";${Nasm}"
|
||||
choco install -y nasm --version=2.16.01
|
||||
$env:BUN_DEPS_OUT_DIR = (mkdir -Force "./bun-deps")
|
||||
.\scripts\all-dependencies.ps1
|
||||
- name: Save Cache
|
||||
|
||||
@@ -117,8 +117,11 @@ pub const Blob = struct {
|
||||
pub const SizeType = u52;
|
||||
pub const max_size = std.math.maxInt(SizeType);
|
||||
|
||||
const serialization_version: u8 = 1;
|
||||
const reserved_space_for_serialization: u32 = 128;
|
||||
/// 1: Initial
|
||||
/// 2: Added byte for whether it's a dom file, length and bytes for `stored_name`,
|
||||
/// and f64 for `last_modified`. Removed reserved bytes, it's handled by version
|
||||
/// number.
|
||||
const serialization_version: u8 = 2;
|
||||
|
||||
pub fn getFormDataEncoding(this: *Blob) ?*bun.FormData.AsyncFormData {
|
||||
var content_type_slice: ZigString.Slice = this.getContentType() orelse return null;
|
||||
@@ -319,10 +322,10 @@ pub const Blob = struct {
|
||||
) !void {
|
||||
try writer.writeInt(u8, serialization_version, .little);
|
||||
|
||||
try writer.writeInt(u64, @as(u64, @intCast(this.offset)), .little);
|
||||
try writer.writeInt(u64, @intCast(this.offset), .little);
|
||||
|
||||
try writer.writeInt(u32, @as(u32, @truncate(this.content_type.len)), .little);
|
||||
_ = try writer.write(this.content_type);
|
||||
try writer.writeInt(u32, @truncate(this.content_type.len), .little);
|
||||
try writer.writeAll(this.content_type);
|
||||
try writer.writeInt(u8, @intFromBool(this.content_type_was_set), .little);
|
||||
|
||||
const store_tag: Store.SerializeTag = if (this.store) |store|
|
||||
@@ -337,8 +340,8 @@ pub const Blob = struct {
|
||||
try store.serialize(Writer, writer);
|
||||
}
|
||||
|
||||
// reserved space for future use
|
||||
_ = try writer.write(&[_]u8{0} ** reserved_space_for_serialization);
|
||||
try writer.writeInt(u8, @intFromBool(this.is_jsdom_file), .little);
|
||||
try writeFloat(f64, this.last_modified, Writer, writer);
|
||||
}
|
||||
|
||||
pub fn onStructuredCloneSerialize(
|
||||
@@ -372,6 +375,25 @@ pub const Blob = struct {
|
||||
_ = globalThis;
|
||||
}
|
||||
|
||||
fn writeFloat(
|
||||
comptime FloatType: type,
|
||||
value: FloatType,
|
||||
comptime Writer: type,
|
||||
writer: Writer,
|
||||
) !void {
|
||||
const bytes: [@sizeOf(FloatType)]u8 = @bitCast(value);
|
||||
try writer.writeAll(&bytes);
|
||||
}
|
||||
|
||||
fn readFloat(
|
||||
comptime FloatType: type,
|
||||
comptime Reader: type,
|
||||
reader: Reader,
|
||||
) !FloatType {
|
||||
const bytes = try reader.readBoundedBytes(@sizeOf(FloatType));
|
||||
return @bitCast(bytes.slice()[0..@sizeOf(FloatType)].*);
|
||||
}
|
||||
|
||||
fn readSlice(
|
||||
reader: anytype,
|
||||
len: usize,
|
||||
@@ -391,7 +413,6 @@ pub const Blob = struct {
|
||||
const allocator = bun.default_allocator;
|
||||
|
||||
const version = try reader.readInt(u8, .little);
|
||||
_ = version;
|
||||
|
||||
const offset = try reader.readInt(u64, .little);
|
||||
|
||||
@@ -404,16 +425,29 @@ pub const Blob = struct {
|
||||
const store_tag = try reader.readEnum(Store.SerializeTag, .little);
|
||||
|
||||
const blob: *Blob = switch (store_tag) {
|
||||
.bytes => brk: {
|
||||
.bytes => bytes: {
|
||||
const bytes_len = try reader.readInt(u32, .little);
|
||||
const bytes = try readSlice(reader, bytes_len, allocator);
|
||||
|
||||
const blob = Blob.init(bytes, allocator, globalThis);
|
||||
const blob_ = Blob.new(blob);
|
||||
|
||||
break :brk blob_;
|
||||
versions: {
|
||||
if (version == 1) break :versions;
|
||||
|
||||
const name_len = try reader.readInt(u32, .little);
|
||||
const name = try readSlice(reader, name_len, allocator);
|
||||
|
||||
if (blob.store) |store| switch (store.data) {
|
||||
.bytes => |*bytes_store| bytes_store.stored_name = bun.PathString.init(name),
|
||||
else => {},
|
||||
};
|
||||
|
||||
if (version == 2) break :versions;
|
||||
}
|
||||
|
||||
break :bytes Blob.new(blob);
|
||||
},
|
||||
.file => brk: {
|
||||
.file => file: {
|
||||
const pathlike_tag = try reader.readEnum(JSC.Node.PathOrFileDescriptor.SerializeTag, .little);
|
||||
|
||||
switch (pathlike_tag) {
|
||||
@@ -428,7 +462,7 @@ pub const Blob = struct {
|
||||
globalThis,
|
||||
));
|
||||
|
||||
break :brk blob;
|
||||
break :file blob;
|
||||
},
|
||||
.path => {
|
||||
const path_len = try reader.readInt(u32, .little);
|
||||
@@ -444,16 +478,24 @@ pub const Blob = struct {
|
||||
globalThis,
|
||||
));
|
||||
|
||||
break :brk blob;
|
||||
break :file blob;
|
||||
},
|
||||
}
|
||||
|
||||
return .zero;
|
||||
},
|
||||
.empty => brk: {
|
||||
break :brk Blob.new(Blob.initEmpty(globalThis));
|
||||
},
|
||||
.empty => Blob.new(Blob.initEmpty(globalThis)),
|
||||
};
|
||||
|
||||
versions: {
|
||||
if (version == 1) break :versions;
|
||||
|
||||
blob.is_jsdom_file = try reader.readInt(u8, .little) != 0;
|
||||
blob.last_modified = try readFloat(f64, Reader, reader);
|
||||
|
||||
if (version == 2) break :versions;
|
||||
}
|
||||
|
||||
blob.allocator = allocator;
|
||||
blob.offset = @as(u52, @intCast(offset));
|
||||
if (content_type.len > 0) {
|
||||
@@ -474,13 +516,7 @@ pub const Blob = struct {
|
||||
var buffer_stream = std.io.fixedBufferStream(ptr[0..total_length]);
|
||||
const reader = buffer_stream.reader();
|
||||
|
||||
const blob = _onStructuredCloneDeserialize(globalThis, @TypeOf(reader), reader) catch return .zero;
|
||||
|
||||
if (Environment.allow_assert) {
|
||||
assert(total_length - reader.context.pos == reserved_space_for_serialization);
|
||||
}
|
||||
|
||||
return blob;
|
||||
return _onStructuredCloneDeserialize(globalThis, @TypeOf(reader), reader) catch return .zero;
|
||||
}
|
||||
|
||||
const URLSearchParamsConverter = struct {
|
||||
@@ -1380,6 +1416,8 @@ pub const Blob = struct {
|
||||
}
|
||||
}
|
||||
|
||||
var set_last_modified = false;
|
||||
|
||||
if (args.len > 2) {
|
||||
const options = args[2];
|
||||
if (options.isObject()) {
|
||||
@@ -1410,11 +1448,18 @@ pub const Blob = struct {
|
||||
}
|
||||
|
||||
if (options.getTruthy(globalThis, "lastModified")) |last_modified| {
|
||||
set_last_modified = true;
|
||||
blob.last_modified = last_modified.coerce(f64, globalThis);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!set_last_modified) {
|
||||
// `lastModified` should be the current date in milliseconds if unspecified.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/File/lastModified
|
||||
blob.last_modified = @floatFromInt(std.time.milliTimestamp());
|
||||
}
|
||||
|
||||
if (blob.content_type.len == 0) {
|
||||
blob.content_type = "";
|
||||
blob.content_type_was_set = false;
|
||||
@@ -1707,14 +1752,17 @@ pub const Blob = struct {
|
||||
.path => |path| {
|
||||
const path_slice = path.slice();
|
||||
try writer.writeInt(u32, @as(u32, @truncate(path_slice.len)), .little);
|
||||
_ = try writer.write(path_slice);
|
||||
try writer.writeAll(path_slice);
|
||||
},
|
||||
}
|
||||
},
|
||||
.bytes => |bytes| {
|
||||
const slice = bytes.slice();
|
||||
try writer.writeInt(u32, @as(u32, @truncate(slice.len)), .little);
|
||||
_ = try writer.write(slice);
|
||||
try writer.writeInt(u32, @truncate(slice.len), .little);
|
||||
try writer.writeAll(slice);
|
||||
|
||||
try writer.writeInt(u32, @truncate(bytes.stored_name.slice().len), .little);
|
||||
try writer.writeAll(bytes.stored_name.slice());
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ describe("File", () => {
|
||||
expect(file.name).toBe("bar.txt");
|
||||
expect(file.type).toBe("text/plain;charset=utf-8");
|
||||
expect(file.size).toBe(3);
|
||||
expect(file.lastModified).toBe(0);
|
||||
expect(file.lastModified).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("constructor with lastModified", () => {
|
||||
@@ -77,7 +77,7 @@ describe("File", () => {
|
||||
expect(file.name).toBe("undefined");
|
||||
expect(file.type).toBe("");
|
||||
expect(file.size).toBe(3);
|
||||
expect(file.lastModified).toBe(0);
|
||||
expect(file.lastModified).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("constructor throws invalid args", () => {
|
||||
@@ -129,7 +129,7 @@ describe("File", () => {
|
||||
expect(foo.name).toBe("bar.txt");
|
||||
expect(foo.type).toBe("text/plain;charset=utf-8");
|
||||
expect(foo.size).toBe(3);
|
||||
expect(foo.lastModified).toBe(0);
|
||||
expect(foo.lastModified).toBeGreaterThanOrEqual(0);
|
||||
expect(await foo.text()).toBe("foo");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -166,6 +166,28 @@ describe("structured clone", () => {
|
||||
expect(cloned.lastModified).toBe(blob.lastModified);
|
||||
expect(cloned.name).toBe(blob.name);
|
||||
});
|
||||
describe("dom file", async () => {
|
||||
test("without lastModified", async () => {
|
||||
const file = new File(["hi"], "example.txt", { type: "text/plain" });
|
||||
expect(file.lastModified).toBeGreaterThan(0);
|
||||
expect(file.name).toBe("example.txt");
|
||||
expect(file.size).toBe(2);
|
||||
const cloned = structuredClone(file);
|
||||
expect(cloned.lastModified).toBe(file.lastModified);
|
||||
expect(cloned.name).toBe(file.name);
|
||||
expect(cloned.size).toBe(file.size);
|
||||
});
|
||||
test("with lastModified", async () => {
|
||||
const file = new File(["hi"], "example.txt", { type: "text/plain", lastModified: 123 });
|
||||
expect(file.lastModified).toBe(123);
|
||||
expect(file.name).toBe("example.txt");
|
||||
expect(file.size).toBe(2);
|
||||
const cloned = structuredClone(file);
|
||||
expect(cloned.lastModified).toBe(123);
|
||||
expect(cloned.name).toBe(file.name);
|
||||
expect(cloned.size).toBe(file.size);
|
||||
});
|
||||
});
|
||||
test("unpaired high surrogate (invalid utf-8)", async () => {
|
||||
const blob = createBlob(encode_cesu8([0xd800]));
|
||||
const cloned = structuredClone(blob);
|
||||
|
||||
Reference in New Issue
Block a user