Compare commits

...

13 Commits

Author SHA1 Message Date
Jarred Sumner
7bb4d31f43 Fix bug in test runner 2023-03-08 00:18:08 -08:00
Jarred Sumner
f3cd2be68c Fix .eql check on empty values 2023-03-07 23:29:07 -08:00
Jarred Sumner
dec6c62968 Update blob.zig 2023-03-07 22:40:00 -08:00
Jarred Sumner
d728839497 Update blob.zig 2023-03-07 22:39:48 -08:00
Jarred Sumner
8d877e288b Fix failing type test 2023-03-07 22:38:26 -08:00
Jarred Sumner
cb8af3a4e7 Fix types 2023-03-07 22:34:28 -08:00
Jarred Sumner
5b0277b6b8 Add isASCII check 2023-03-07 22:34:24 -08:00
Jarred Sumner
88ad5ed73c Make Blob.prototype.type more spec compliant 2023-03-07 22:16:55 -08:00
Jarred Sumner
4d3eefa45f More tests for blob.slice 2023-03-07 21:56:21 -08:00
Jarred Sumner
7a748678ae Safer JSValue.isString() 2023-03-07 21:56:08 -08:00
Jarred Sumner
43b0d03482 Fix make headers 2023-03-07 21:46:38 -08:00
Jarred Sumner
7ac6eab209 Add a few more checks for isNumber() 2023-03-07 18:44:48 -08:00
Jarred Sumner
16207d78be Make Blob.prototype. type more spec compliant 2023-03-07 18:44:48 -08:00
17 changed files with 262 additions and 64 deletions

View File

@@ -55,7 +55,7 @@ async function runTest(path) {
stdout,
stderr,
status: exitCode,
} = spawnSync("bun", ["test", basename(path)], {
} = spawnSync("bun", ["test", path], {
stdio: ["ignore", "pipe", "pipe"],
timeout: 10_000,
env: {

View File

@@ -620,8 +620,26 @@ declare module "bun" {
*
* @param begin - start offset in bytes
* @param end - absolute offset in bytes (relative to 0)
* @param contentType - MIME type for the new FileBlob
*/
slice(begin?: number, end?: number): FileBlob;
slice(begin?: number, end?: number, contentType?: string): FileBlob;
/**
* Offset any operation on the file starting at `begin`
*
* Similar to [`TypedArray.subarray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/subarray). Does not copy the file, open the file, or modify the file.
*
* If `begin` > 0, {@link Bun.write()} will be slower on macOS
*
* @param begin - start offset in bytes
* @param contentType - MIME type for the new FileBlob
*/
slice(begin?: number, contentType?: string): FileBlob;
/**
* @param contentType - MIME type for the new FileBlob
*/
slice(contentType?: string): FileBlob;
/**
* Incremental writer for files and pipes.

View File

@@ -565,7 +565,29 @@ declare class Blob implements BlobInterface {
* @param end The index that sets the end of the view.
*
*/
slice(begin?: number, end?: number): Blob;
slice(begin?: number, end?: number, contentType?: string): Blob;
/**
* Create a new view **without 🚫 copying** the underlying data.
*
* Similar to [`BufferSource.subarray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BufferSource/subarray)
*
* @param begin The index that sets the beginning of the view.
* @param end The index that sets the end of the view.
*
*/
slice(begin?: number, contentType?: string): Blob;
/**
* Create a new view **without 🚫 copying** the underlying data.
*
* Similar to [`BufferSource.subarray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BufferSource/subarray)
*
* @param begin The index that sets the beginning of the view.
* @param end The index that sets the end of the view.
*
*/
slice(contentType?: string): Blob;
/**
* Read the data from the blob as a string. It will be decoded from UTF-8.

View File

@@ -13,6 +13,4 @@ async function* listReleases() {
}
}
const releases = await Array.fromAsync(listReleases());
export {};
export const releases = await Array.fromAsync(listReleases());

View File

@@ -377,7 +377,9 @@ pub const ServerConfig = struct {
}
if (arg.getTruthy(global, "maxRequestBodySize")) |max_request_body_size| {
args.max_request_body_size = @intCast(u64, @max(0, max_request_body_size.toInt64()));
if (max_request_body_size.isNumber()) {
args.max_request_body_size = @intCast(u64, @max(0, max_request_body_size.toInt64()));
}
}
if (arg.getTruthy(global, "error")) |onError| {

View File

@@ -1773,6 +1773,8 @@ pub const ArrayBuffer = extern struct {
}
pub fn createEmpty(globalThis: *JSC.JSGlobalObject, comptime kind: JSC.JSValue.JSType) JSValue {
JSC.markBinding(@src());
return switch (comptime kind) {
.Uint8Array => Bun__createUint8ArrayForCopy(globalThis, null, 0, false),
.ArrayBuffer => Bun__createArrayBufferForCopy(globalThis, null, 0),

View File

@@ -2455,10 +2455,6 @@ bool JSC__JSValue__isPrimitive(JSC__JSValue JSValue0)
{
return JSC::JSValue::decode(JSValue0).isPrimitive();
}
bool JSC__JSValue__isString(JSC__JSValue JSValue0)
{
return JSC::JSValue::decode(JSValue0).isString();
}
bool JSC__JSValue__isSymbol(JSC__JSValue JSValue0)
{
return JSC::JSValue::decode(JSValue0).isSymbol();

View File

@@ -159,6 +159,9 @@ pub const ZigString = extern struct {
}
pub fn eql(this: ZigString, other: ZigString) bool {
if (this.len == 0 or other.len == 0)
return this.len == other.len;
const left_utf16 = this.is16Bit();
const right_utf16 = other.is16Bit();
@@ -2276,6 +2279,10 @@ pub const JSGlobalObject = extern struct {
return this.bunVM().allocator;
}
pub fn throwOutOfMemory(this: *JSGlobalObject) void {
this.throwValue(this.createErrorInstance("Out of memory", .{}));
}
pub fn throwInvalidArguments(
this: *JSGlobalObject,
comptime fmt: string,
@@ -3454,8 +3461,11 @@ pub const JSValue = enum(JSValueReprInt) {
return res;
}
pub fn isString(this: JSValue) bool {
return cppFn("isString", .{this});
pub inline fn isString(this: JSValue) bool {
if (!this.isCell())
return false;
return jsType(this).isStringLike();
}
pub fn isBigInt(this: JSValue) bool {
return cppFn("isBigInt", .{this});
@@ -3968,7 +3978,6 @@ pub const JSValue = enum(JSValueReprInt) {
"isObject",
"isPrimitive",
"isSameValue",
"isString",
"isSymbol",
"isTerminationException",
"isUInt32AsAnyInt",

View File

@@ -1,4 +1,4 @@
//-- AUTOGENERATED FILE -- 1677776166
//-- AUTOGENERATED FILE -- 1678254453
// clang-format off
#pragma once

View File

@@ -1,5 +1,5 @@
// clang-format off
//-- AUTOGENERATED FILE -- 1677776166
//-- AUTOGENERATED FILE -- 1678254453
#pragma once
#include <stddef.h>
@@ -331,7 +331,6 @@ CPP_DECL bool JSC__JSValue__isNumber(JSC__JSValue JSValue0);
CPP_DECL bool JSC__JSValue__isObject(JSC__JSValue JSValue0);
CPP_DECL bool JSC__JSValue__isPrimitive(JSC__JSValue JSValue0);
CPP_DECL bool JSC__JSValue__isSameValue(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* arg2);
CPP_DECL bool JSC__JSValue__isString(JSC__JSValue JSValue0);
CPP_DECL bool JSC__JSValue__isSymbol(JSC__JSValue JSValue0);
CPP_DECL bool JSC__JSValue__isTerminationException(JSC__JSValue JSValue0, JSC__VM* arg1);
CPP_DECL bool JSC__JSValue__isUInt32AsAnyInt(JSC__JSValue JSValue0);

View File

@@ -244,7 +244,6 @@ pub extern fn JSC__JSValue__isNumber(JSValue0: JSC__JSValue) bool;
pub extern fn JSC__JSValue__isObject(JSValue0: JSC__JSValue) bool;
pub extern fn JSC__JSValue__isPrimitive(JSValue0: JSC__JSValue) bool;
pub extern fn JSC__JSValue__isSameValue(JSValue0: JSC__JSValue, JSValue1: JSC__JSValue, arg2: *bindings.JSGlobalObject) bool;
pub extern fn JSC__JSValue__isString(JSValue0: JSC__JSValue) bool;
pub extern fn JSC__JSValue__isSymbol(JSValue0: JSC__JSValue) bool;
pub extern fn JSC__JSValue__isTerminationException(JSValue0: JSC__JSValue, arg1: *bindings.VM) bool;
pub extern fn JSC__JSValue__isUInt32AsAnyInt(JSValue0: JSC__JSValue) bool;

View File

@@ -451,6 +451,10 @@ pub const VirtualMachine = struct {
return VMHolder.vm.?;
}
pub fn mimeType(this: *VirtualMachine, str: []const u8) ?bun.HTTP.MimeType {
return this.rareData().mimeTypeFromString(this.allocator, str);
}
pub const GCLevel = enum(u3) {
none = 0,
mild = 1,

View File

@@ -27,6 +27,18 @@ file_polls_: ?*JSC.FilePoll.HiveArray = null,
global_dns_data: ?*JSC.DNS.GlobalData = null,
mime_types: ?bun.HTTP.MimeType.Map = null,
pub fn mimeTypeFromString(this: *RareData, allocator: std.mem.Allocator, str: []const u8) ?bun.HTTP.MimeType {
if (this.mime_types == null) {
this.mime_types = bun.HTTP.MimeType.createHashTable(
allocator,
) catch @panic("Out of memory");
}
return this.mime_types.?.get(str);
}
pub fn filePolls(this: *RareData, vm: *JSC.VirtualMachine) *JSC.FilePoll.HiveArray {
return this.file_polls_ orelse {
this.file_polls_ = vm.allocator.create(JSC.FilePoll.HiveArray) catch unreachable;

View File

@@ -2391,7 +2391,7 @@ pub const Blob = struct {
callframe: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
var allocator = globalThis.allocator();
var arguments_ = callframe.arguments(2);
var arguments_ = callframe.arguments(3);
var args = arguments_.ptr[0..arguments_.len];
if (this.size == 0) {
@@ -2410,51 +2410,84 @@ pub const Blob = struct {
// 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);
if (args.ptr[0].isString()) {
args.ptr[2] = args.ptr[0];
args.ptr[1] = .zero;
args.ptr[0] = .zero;
args.len = 3;
} else if (args.ptr[1].isString()) {
args.ptr[2] = args.ptr[1];
args.ptr[1] = .zero;
args.len = 3;
}
var args_iter = JSC.Node.ArgumentsSlice.init(globalThis.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, @max(start + @intCast(i64, this.size), 0));
} else {
// Otherwise, let relativeStart be start.
relativeStart = @min(@intCast(i64, start), @intCast(i64, this.size));
if (start_.isNumber()) {
const start = start_.toInt64();
if (start < 0) {
// If the optional start parameter is negative, let relativeStart be start + size.
relativeStart = @intCast(i64, @max(start +% @intCast(i64, this.size), 0));
} else {
// Otherwise, let relativeStart be start.
relativeStart = @min(@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, @max(end + @intCast(i64, this.size), 0));
} else {
// Otherwise, let relativeStart be start.
relativeEnd = @min(@intCast(i64, end), @intCast(i64, this.size));
if (end_.isNumber()) {
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, @max(end +% @intCast(i64, this.size), 0));
} else {
// Otherwise, let relativeStart be start.
relativeEnd = @min(@intCast(i64, end), @intCast(i64, this.size));
}
}
}
var content_type: string = "";
var content_type_was_allocated = false;
if (args_iter.nextEat()) |content_type_| {
if (content_type_.isString()) {
var zig_str = content_type_.getZigString(globalThis);
var slicer = zig_str.toSlice(bun.default_allocator);
defer slicer.deinit();
var slice = slicer.slice();
var content_type_buf = allocator.alloc(u8, slice.len) catch unreachable;
content_type = strings.copyLowercase(slice, content_type_buf);
inner: {
if (content_type_.isString()) {
var zig_str = content_type_.getZigString(globalThis);
var slicer = zig_str.toSlice(bun.default_allocator);
defer slicer.deinit();
var slice = slicer.slice();
if (!strings.isAllASCII(slice)) {
break :inner;
}
if (globalThis.bunVM().mimeType(slice)) |mime| {
content_type = mime.value;
break :inner;
}
content_type_was_allocated = slice.len > 0;
var content_type_buf = allocator.alloc(u8, slice.len) catch unreachable;
content_type = strings.copyLowercase(slice, content_type_buf);
}
}
}
const len = @intCast(SizeType, @max(relativeEnd - relativeStart, 0));
const len = @intCast(SizeType, @max(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;
// infer the content type if it was not specified
if (content_type.len == 0 and this.content_type.len > 0 and !this.content_type_allocated)
content_type = this.content_type;
blob.content_type = content_type;
blob.content_type_allocated = content_type.len > 0;
blob.content_type_allocated = content_type_was_allocated;
var blob_ = allocator.create(Blob) catch unreachable;
blob_.* = blob;
@@ -2474,19 +2507,35 @@ pub const Blob = struct {
globalThis: *JSC.JSGlobalObject,
value: JSC.JSValue,
) callconv(.C) bool {
var zig_str = value.getZigString(globalThis);
if (zig_str.is16Bit())
return false;
var zig_str = if (value.isString())
value.getZigString(globalThis)
else
ZigString.Empty;
var slice = zig_str.trimmedSlice();
if (strings.eql(slice, this.content_type))
if (!zig_str.isAllASCII()) {
zig_str = ZigString.Empty;
}
if (zig_str.eql(ZigString.init(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 = globalThis.allocator().alloc(u8, slice.len) catch unreachable;
this.content_type = strings.copyLowercase(slice, content_type_buf);
var slicer = zig_str.toSlice(bun.default_allocator);
defer slicer.deinit();
const allocated = this.content_type_allocated;
defer if (allocated) bun.default_allocator.free(prev_content_type);
if (globalThis.bunVM().mimeType(slicer.slice())) |mime| {
this.content_type = mime.value;
this.content_type_allocated = false;
return true;
}
var content_type_buf = globalThis.allocator().alloc(u8, slicer.len) catch {
globalThis.throwOutOfMemory();
return false;
};
this.content_type = strings.copyLowercase(slicer.slice(), content_type_buf);
}
this.content_type_allocated = true;
@@ -2602,13 +2651,22 @@ pub const Blob = struct {
// Normative conditions for this member are provided
// in the §3.1 Constructors.
if (options.get(globalThis, "type")) |content_type| {
if (content_type.isString()) {
var content_type_str = content_type.toSlice(globalThis, bun.default_allocator);
defer content_type_str.deinit();
var slice = content_type_str.slice();
var content_type_buf = allocator.alloc(u8, slice.len) catch unreachable;
blob.content_type = strings.copyLowercase(slice, content_type_buf);
blob.content_type_allocated = true;
inner: {
if (content_type.isString()) {
var content_type_str = content_type.toSlice(globalThis, bun.default_allocator);
defer content_type_str.deinit();
var slice = content_type_str.slice();
if (!strings.isAllASCII(slice)) {
break :inner;
}
if (globalThis.bunVM().mimeType(slice)) |mime| {
blob.content_type = mime.value;
break :inner;
}
var content_type_buf = allocator.alloc(u8, slice.len) catch unreachable;
blob.content_type = strings.copyLowercase(slice, content_type_buf);
blob.content_type_allocated = true;
}
}
}
}

View File

@@ -399,7 +399,8 @@ pub const StreamStart = union(Tag) {
}
if (value.get(globalThis, "chunkSize")) |chunkSize| {
return .{ .chunk_size = @intCast(Blob.SizeType, @truncate(i52, chunkSize.toInt64())) };
if (chunkSize.isNumber())
return .{ .chunk_size = @intCast(Blob.SizeType, @truncate(i52, chunkSize.toInt64())) };
}
return .{ .empty = {} };
@@ -432,8 +433,10 @@ pub const StreamStart = union(Tag) {
}
if (value.get(globalThis, "highWaterMark")) |chunkSize| {
empty = false;
chunk_size = @intCast(JSC.WebCore.Blob.SizeType, @max(0, @truncate(i51, chunkSize.toInt64())));
if (chunkSize.isNumber()) {
empty = false;
chunk_size = @intCast(JSC.WebCore.Blob.SizeType, @max(0, @truncate(i51, chunkSize.toInt64())));
}
}
if (!empty) {
@@ -450,7 +453,8 @@ pub const StreamStart = union(Tag) {
var chunk_size: JSC.WebCore.Blob.SizeType = 0;
if (value.get(globalThis, "highWaterMark")) |chunkSize| {
chunk_size = @intCast(JSC.WebCore.Blob.SizeType, @max(0, @truncate(i51, chunkSize.toInt64())));
if (chunkSize.isNumber())
chunk_size = @intCast(JSC.WebCore.Blob.SizeType, @max(0, @truncate(i51, chunkSize.toInt64())));
}
if (value.get(globalThis, "path")) |path| {
@@ -485,8 +489,10 @@ pub const StreamStart = union(Tag) {
var chunk_size: JSC.WebCore.Blob.SizeType = 2048;
if (value.get(globalThis, "highWaterMark")) |chunkSize| {
empty = false;
chunk_size = @intCast(JSC.WebCore.Blob.SizeType, @max(256, @truncate(i51, chunkSize.toInt64())));
if (chunkSize.isNumber()) {
empty = false;
chunk_size = @intCast(JSC.WebCore.Blob.SizeType, @max(256, @truncate(i51, chunkSize.toInt64())));
}
}
if (!empty) {

View File

@@ -18,6 +18,23 @@ const MimeType = @This();
value: string,
category: Category,
pub const Map = bun.StringHashMap(MimeType);
pub fn createHashTable(allocator: std.mem.Allocator) !Map {
@setCold(true);
const decls = comptime std.meta.declarations(all);
var map = Map.init(allocator);
try map.ensureTotalCapacity(@truncate(u32, decls.len));
@setEvalBranchQuota(4000);
inline for (decls) |decl| {
map.putAssumeCapacityNoClobber(decl.name, @field(all, decl.name));
}
return map;
}
pub fn canOpenInEditor(this: MimeType) bool {
if (this.category == .text or this.category.isCode())
return true;

View File

@@ -0,0 +1,56 @@
import { test, expect } from "bun:test";
test("Blob.slice", () => {
const blob = new Blob(["Bun", "Foo"]);
const b1 = blob.slice(0, 3, "Text/HTML");
expect(b1 instanceof Blob).toBeTruthy();
expect(b1.size).toBe(3);
expect(b1.type).toBe("text/html");
const b2 = blob.slice(-1, 3);
expect(b2.size).toBe(0);
const b3 = blob.slice(100, 3);
expect(b3.size).toBe(0);
const b4 = blob.slice(0, 10);
expect(b4.size).toBe(blob.size);
expect(blob.slice().size).toBe(blob.size);
expect(blob.slice(0).size).toBe(blob.size);
expect(blob.slice(NaN).size).toBe(blob.size);
expect(blob.slice(0, Infinity).size).toBe(blob.size);
expect(blob.slice(-Infinity).size).toBe(blob.size);
expect(blob.slice(0, NaN).size).toBe(0);
// @ts-expect-error
expect(blob.slice(Symbol(), "-123").size).toBe(6);
expect(blob.slice(Object.create(null), "-123").size).toBe(6);
// @ts-expect-error
expect(blob.slice(null, "-123").size).toBe(6);
expect(blob.slice(0, 10).size).toBe(blob.size);
expect(blob.slice("text/plain;charset=utf-8").type).toBe("text/plain;charset=utf-8");
});
test("Blob.prototype.type setter", () => {
var blob = new Blob(["Bun", "Foo"], { type: "text/foo" });
expect(blob.type).toBe("text/foo");
blob.type = "text/bar";
expect(blob.type).toBe("text/bar");
blob.type = "text/baz";
expect(blob.type).toBe("text/baz");
blob.type = "text/baz; charset=utf-8";
expect(blob.type).toBe("text/baz; charset=utf-8");
// @ts-expect-error
blob.type = NaN;
expect(blob.type).toBe("");
// @ts-expect-error
blob.type = Symbol();
expect(blob.type).toBe("");
});
test("new Blob", () => {
var blob = new Blob(["Bun", "Foo"], { type: "text/foo" });
expect(blob.size).toBe(6);
expect(blob.type).toBe("text/foo");
blob = new Blob(["Bun", "Foo"], { type: "\u1234" });
expect(blob.size).toBe(6);
expect(blob.type).toBe("");
});