Skip to content

Commit

Permalink
More spec compliant Blob.prototype.type (#2340)
Browse files Browse the repository at this point in the history
* Make `Blob.prototype. type` more spec compliant

* Add a few more checks for isNumber()

* Fix `make headers`

* Safer JSValue.isString()

* More tests for blob.slice

* Make `Blob.prototype.type` more spec compliant

* Add isASCII check

* Fix types

* Fix failing type test

* Update blob.zig

* Update blob.zig

* Fix .eql check on empty values

---------

Co-authored-by: Jarred Sumner <[email protected]>
  • Loading branch information
Jarred-Sumner and Jarred-Sumner committed Mar 8, 2023
1 parent c04f582 commit f97c8ac
Show file tree
Hide file tree
Showing 16 changed files with 261 additions and 63 deletions.
20 changes: 19 additions & 1 deletion packages/bun-types/bun.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
24 changes: 23 additions & 1 deletion packages/bun-types/globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 1 addition & 3 deletions packages/bun-types/tests/array.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,4 @@ async function* listReleases() {
}
}

const releases = await Array.fromAsync(listReleases());

export {};
export const releases = await Array.fromAsync(listReleases());
4 changes: 3 additions & 1 deletion src/bun.js/api/server.zig
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,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| {
Expand Down
2 changes: 2 additions & 0 deletions src/bun.js/base.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
4 changes: 0 additions & 4 deletions src/bun.js/bindings/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
15 changes: 12 additions & 3 deletions src/bun.js/bindings/bindings.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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});
Expand Down Expand Up @@ -3968,7 +3978,6 @@ pub const JSValue = enum(JSValueReprInt) {
"isObject",
"isPrimitive",
"isSameValue",
"isString",
"isSymbol",
"isTerminationException",
"isUInt32AsAnyInt",
Expand Down
2 changes: 1 addition & 1 deletion src/bun.js/bindings/headers-cpp.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//-- AUTOGENERATED FILE -- 1677776166
//-- AUTOGENERATED FILE -- 1678254453
// clang-format off
#pragma once

Expand Down
3 changes: 1 addition & 2 deletions src/bun.js/bindings/headers.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// clang-format off
//-- AUTOGENERATED FILE -- 1677776166
//-- AUTOGENERATED FILE -- 1678254453
#pragma once

#include <stddef.h>
Expand Down Expand Up @@ -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);
Expand Down
1 change: 0 additions & 1 deletion src/bun.js/bindings/headers.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions src/bun.js/javascript.zig
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,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,
Expand Down
12 changes: 12 additions & 0 deletions src/bun.js/rare_data.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
138 changes: 98 additions & 40 deletions src/bun.js/webcore/blob.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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;
Expand All @@ -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;

if (!zig_str.isAllASCII()) {
zig_str = ZigString.Empty;
}

var slice = zig_str.trimmedSlice();
if (strings.eql(slice, this.content_type))
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;
Expand Down Expand Up @@ -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;
}
}
}
}
Expand Down
Loading

0 comments on commit f97c8ac

Please sign in to comment.