mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
Split bun crypto APIs into more files (#18431)
This commit is contained in:
File diff suppressed because it is too large
Load Diff
638
src/bun.js/api/FFIObject.zig
Normal file
638
src/bun.js/api/FFIObject.zig
Normal file
@@ -0,0 +1,638 @@
|
||||
pub fn newCString(globalThis: *JSGlobalObject, value: JSValue, byteOffset: ?JSValue, lengthValue: ?JSValue) JSC.JSValue {
|
||||
switch (FFIObject.getPtrSlice(globalThis, value, byteOffset, lengthValue)) {
|
||||
.err => |err| {
|
||||
return err;
|
||||
},
|
||||
.slice => |slice| {
|
||||
return bun.String.createUTF8ForJS(globalThis, slice);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub const dom_call = JSC.DOMCall("FFI", @This(), "ptr", JSC.DOMEffect.forRead(.TypedArrayProperties));
|
||||
|
||||
pub fn toJS(globalObject: *JSC.JSGlobalObject) JSC.JSValue {
|
||||
const object = JSC.JSValue.createEmptyObject(globalObject, comptime std.meta.fieldNames(@TypeOf(fields)).len + 2);
|
||||
inline for (comptime std.meta.fieldNames(@TypeOf(fields))) |field| {
|
||||
object.put(
|
||||
globalObject,
|
||||
comptime ZigString.static(field),
|
||||
JSC.createCallback(globalObject, comptime ZigString.static(field), 1, comptime @field(fields, field)),
|
||||
);
|
||||
}
|
||||
|
||||
dom_call.put(globalObject, object);
|
||||
object.put(globalObject, ZigString.static("read"), Reader.toJS(globalObject));
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
pub const Reader = struct {
|
||||
pub const DOMCalls = .{
|
||||
.u8 = JSC.DOMCall("Reader", @This(), "u8", JSC.DOMEffect.forRead(.World)),
|
||||
.u16 = JSC.DOMCall("Reader", @This(), "u16", JSC.DOMEffect.forRead(.World)),
|
||||
.u32 = JSC.DOMCall("Reader", @This(), "u32", JSC.DOMEffect.forRead(.World)),
|
||||
.ptr = JSC.DOMCall("Reader", @This(), "ptr", JSC.DOMEffect.forRead(.World)),
|
||||
.i8 = JSC.DOMCall("Reader", @This(), "i8", JSC.DOMEffect.forRead(.World)),
|
||||
.i16 = JSC.DOMCall("Reader", @This(), "i16", JSC.DOMEffect.forRead(.World)),
|
||||
.i32 = JSC.DOMCall("Reader", @This(), "i32", JSC.DOMEffect.forRead(.World)),
|
||||
.i64 = JSC.DOMCall("Reader", @This(), "i64", JSC.DOMEffect.forRead(.World)),
|
||||
.u64 = JSC.DOMCall("Reader", @This(), "u64", JSC.DOMEffect.forRead(.World)),
|
||||
.intptr = JSC.DOMCall("Reader", @This(), "intptr", JSC.DOMEffect.forRead(.World)),
|
||||
.f32 = JSC.DOMCall("Reader", @This(), "f32", JSC.DOMEffect.forRead(.World)),
|
||||
.f64 = JSC.DOMCall("Reader", @This(), "f64", JSC.DOMEffect.forRead(.World)),
|
||||
};
|
||||
|
||||
pub fn toJS(globalThis: *JSC.JSGlobalObject) JSC.JSValue {
|
||||
const obj = JSC.JSValue.createEmptyObject(globalThis, std.meta.fieldNames(@TypeOf(Reader.DOMCalls)).len);
|
||||
|
||||
inline for (comptime std.meta.fieldNames(@TypeOf(Reader.DOMCalls))) |field| {
|
||||
@field(Reader.DOMCalls, field).put(globalThis, obj);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
pub fn @"u8"(
|
||||
globalObject: *JSGlobalObject,
|
||||
_: JSValue,
|
||||
arguments: []const JSValue,
|
||||
) bun.JSError!JSValue {
|
||||
if (arguments.len == 0 or !arguments[0].isNumber()) {
|
||||
return globalObject.throwInvalidArguments("Expected a pointer", .{});
|
||||
}
|
||||
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
|
||||
const value = @as(*align(1) u8, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
pub fn @"u16"(
|
||||
globalObject: *JSGlobalObject,
|
||||
_: JSValue,
|
||||
arguments: []const JSValue,
|
||||
) bun.JSError!JSValue {
|
||||
if (arguments.len == 0 or !arguments[0].isNumber()) {
|
||||
return globalObject.throwInvalidArguments("Expected a pointer", .{});
|
||||
}
|
||||
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
|
||||
const value = @as(*align(1) u16, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
pub fn @"u32"(
|
||||
globalObject: *JSGlobalObject,
|
||||
_: JSValue,
|
||||
arguments: []const JSValue,
|
||||
) bun.JSError!JSValue {
|
||||
if (arguments.len == 0 or !arguments[0].isNumber()) {
|
||||
return globalObject.throwInvalidArguments("Expected a pointer", .{});
|
||||
}
|
||||
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
|
||||
const value = @as(*align(1) u32, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
pub fn ptr(
|
||||
globalObject: *JSGlobalObject,
|
||||
_: JSValue,
|
||||
arguments: []const JSValue,
|
||||
) bun.JSError!JSValue {
|
||||
if (arguments.len == 0 or !arguments[0].isNumber()) {
|
||||
return globalObject.throwInvalidArguments("Expected a pointer", .{});
|
||||
}
|
||||
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
|
||||
const value = @as(*align(1) u64, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
pub fn @"i8"(
|
||||
globalObject: *JSGlobalObject,
|
||||
_: JSValue,
|
||||
arguments: []const JSValue,
|
||||
) bun.JSError!JSValue {
|
||||
if (arguments.len == 0 or !arguments[0].isNumber()) {
|
||||
return globalObject.throwInvalidArguments("Expected a pointer", .{});
|
||||
}
|
||||
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
|
||||
const value = @as(*align(1) i8, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
pub fn @"i16"(
|
||||
globalObject: *JSGlobalObject,
|
||||
_: JSValue,
|
||||
arguments: []const JSValue,
|
||||
) bun.JSError!JSValue {
|
||||
if (arguments.len == 0 or !arguments[0].isNumber()) {
|
||||
return globalObject.throwInvalidArguments("Expected a pointer", .{});
|
||||
}
|
||||
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
|
||||
const value = @as(*align(1) i16, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
pub fn @"i32"(
|
||||
globalObject: *JSGlobalObject,
|
||||
_: JSValue,
|
||||
arguments: []const JSValue,
|
||||
) bun.JSError!JSValue {
|
||||
if (arguments.len == 0 or !arguments[0].isNumber()) {
|
||||
return globalObject.throwInvalidArguments("Expected a pointer", .{});
|
||||
}
|
||||
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
|
||||
const value = @as(*align(1) i32, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
pub fn intptr(
|
||||
globalObject: *JSGlobalObject,
|
||||
_: JSValue,
|
||||
arguments: []const JSValue,
|
||||
) bun.JSError!JSValue {
|
||||
if (arguments.len == 0 or !arguments[0].isNumber()) {
|
||||
return globalObject.throwInvalidArguments("Expected a pointer", .{});
|
||||
}
|
||||
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
|
||||
const value = @as(*align(1) i64, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
|
||||
pub fn @"f32"(
|
||||
globalObject: *JSGlobalObject,
|
||||
_: JSValue,
|
||||
arguments: []const JSValue,
|
||||
) bun.JSError!JSValue {
|
||||
if (arguments.len == 0 or !arguments[0].isNumber()) {
|
||||
return globalObject.throwInvalidArguments("Expected a pointer", .{});
|
||||
}
|
||||
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
|
||||
const value = @as(*align(1) f32, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
|
||||
pub fn @"f64"(
|
||||
globalObject: *JSGlobalObject,
|
||||
_: JSValue,
|
||||
arguments: []const JSValue,
|
||||
) bun.JSError!JSValue {
|
||||
if (arguments.len == 0 or !arguments[0].isNumber()) {
|
||||
return globalObject.throwInvalidArguments("Expected a pointer", .{});
|
||||
}
|
||||
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
|
||||
const value = @as(*align(1) f64, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
|
||||
pub fn @"i64"(
|
||||
globalObject: *JSGlobalObject,
|
||||
_: JSValue,
|
||||
arguments: []const JSValue,
|
||||
) bun.JSError!JSValue {
|
||||
if (arguments.len == 0 or !arguments[0].isNumber()) {
|
||||
return globalObject.throwInvalidArguments("Expected a pointer", .{});
|
||||
}
|
||||
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
|
||||
const value = @as(*align(1) i64, @ptrFromInt(addr)).*;
|
||||
return JSValue.fromInt64NoTruncate(globalObject, value);
|
||||
}
|
||||
|
||||
pub fn @"u64"(
|
||||
globalObject: *JSGlobalObject,
|
||||
_: JSValue,
|
||||
arguments: []const JSValue,
|
||||
) bun.JSError!JSValue {
|
||||
if (arguments.len == 0 or !arguments[0].isNumber()) {
|
||||
return globalObject.throwInvalidArguments("Expected a pointer", .{});
|
||||
}
|
||||
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
|
||||
const value = @as(*align(1) u64, @ptrFromInt(addr)).*;
|
||||
return JSValue.fromUInt64NoTruncate(globalObject, value);
|
||||
}
|
||||
|
||||
pub fn u8WithoutTypeChecks(
|
||||
_: *JSGlobalObject,
|
||||
_: *anyopaque,
|
||||
raw_addr: i64,
|
||||
offset: i32,
|
||||
) callconv(JSC.conv) JSValue {
|
||||
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
|
||||
const value = @as(*align(1) u8, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
pub fn u16WithoutTypeChecks(
|
||||
_: *JSGlobalObject,
|
||||
_: *anyopaque,
|
||||
raw_addr: i64,
|
||||
offset: i32,
|
||||
) callconv(JSC.conv) JSValue {
|
||||
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
|
||||
const value = @as(*align(1) u16, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
pub fn u32WithoutTypeChecks(
|
||||
_: *JSGlobalObject,
|
||||
_: *anyopaque,
|
||||
raw_addr: i64,
|
||||
offset: i32,
|
||||
) callconv(JSC.conv) JSValue {
|
||||
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
|
||||
const value = @as(*align(1) u32, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
pub fn ptrWithoutTypeChecks(
|
||||
_: *JSGlobalObject,
|
||||
_: *anyopaque,
|
||||
raw_addr: i64,
|
||||
offset: i32,
|
||||
) callconv(JSC.conv) JSValue {
|
||||
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
|
||||
const value = @as(*align(1) u64, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
pub fn i8WithoutTypeChecks(
|
||||
_: *JSGlobalObject,
|
||||
_: *anyopaque,
|
||||
raw_addr: i64,
|
||||
offset: i32,
|
||||
) callconv(JSC.conv) JSValue {
|
||||
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
|
||||
const value = @as(*align(1) i8, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
pub fn i16WithoutTypeChecks(
|
||||
_: *JSGlobalObject,
|
||||
_: *anyopaque,
|
||||
raw_addr: i64,
|
||||
offset: i32,
|
||||
) callconv(JSC.conv) JSValue {
|
||||
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
|
||||
const value = @as(*align(1) i16, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
pub fn i32WithoutTypeChecks(
|
||||
_: *JSGlobalObject,
|
||||
_: *anyopaque,
|
||||
raw_addr: i64,
|
||||
offset: i32,
|
||||
) callconv(JSC.conv) JSValue {
|
||||
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
|
||||
const value = @as(*align(1) i32, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
pub fn intptrWithoutTypeChecks(
|
||||
_: *JSGlobalObject,
|
||||
_: *anyopaque,
|
||||
raw_addr: i64,
|
||||
offset: i32,
|
||||
) callconv(JSC.conv) JSValue {
|
||||
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
|
||||
const value = @as(*align(1) i64, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
|
||||
pub fn f32WithoutTypeChecks(
|
||||
_: *JSGlobalObject,
|
||||
_: *anyopaque,
|
||||
raw_addr: i64,
|
||||
offset: i32,
|
||||
) callconv(JSC.conv) JSValue {
|
||||
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
|
||||
const value = @as(*align(1) f32, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
|
||||
pub fn f64WithoutTypeChecks(
|
||||
_: *JSGlobalObject,
|
||||
_: *anyopaque,
|
||||
raw_addr: i64,
|
||||
offset: i32,
|
||||
) callconv(JSC.conv) JSValue {
|
||||
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
|
||||
const value = @as(*align(1) f64, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
|
||||
pub fn u64WithoutTypeChecks(
|
||||
global: *JSGlobalObject,
|
||||
_: *anyopaque,
|
||||
raw_addr: i64,
|
||||
offset: i32,
|
||||
) callconv(JSC.conv) JSValue {
|
||||
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
|
||||
const value = @as(*align(1) u64, @ptrFromInt(addr)).*;
|
||||
return JSValue.fromUInt64NoTruncate(global, value);
|
||||
}
|
||||
|
||||
pub fn i64WithoutTypeChecks(
|
||||
global: *JSGlobalObject,
|
||||
_: *anyopaque,
|
||||
raw_addr: i64,
|
||||
offset: i32,
|
||||
) callconv(JSC.conv) JSValue {
|
||||
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
|
||||
const value = @as(*align(1) i64, @ptrFromInt(addr)).*;
|
||||
return JSValue.fromInt64NoTruncate(global, value);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn ptr(
|
||||
globalThis: *JSGlobalObject,
|
||||
_: JSValue,
|
||||
arguments: []const JSValue,
|
||||
) JSValue {
|
||||
return switch (arguments.len) {
|
||||
0 => ptr_(globalThis, JSValue.zero, null),
|
||||
1 => ptr_(globalThis, arguments[0], null),
|
||||
else => ptr_(globalThis, arguments[0], arguments[1]),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn ptrWithoutTypeChecks(
|
||||
_: *JSGlobalObject,
|
||||
_: *anyopaque,
|
||||
array: *JSC.JSUint8Array,
|
||||
) callconv(JSC.conv) JSValue {
|
||||
return JSValue.fromPtrAddress(@intFromPtr(array.ptr()));
|
||||
}
|
||||
|
||||
fn ptr_(
|
||||
globalThis: *JSGlobalObject,
|
||||
value: JSValue,
|
||||
byteOffset: ?JSValue,
|
||||
) JSValue {
|
||||
if (value == .zero) {
|
||||
return JSC.JSValue.jsNull();
|
||||
}
|
||||
|
||||
const array_buffer = value.asArrayBuffer(globalThis) orelse {
|
||||
return JSC.toInvalidArguments("Expected ArrayBufferView but received {s}", .{@tagName(value.jsType())}, globalThis);
|
||||
};
|
||||
|
||||
if (array_buffer.len == 0) {
|
||||
return JSC.toInvalidArguments("ArrayBufferView must have a length > 0. A pointer to empty memory doesn't work", .{}, globalThis);
|
||||
}
|
||||
|
||||
var addr: usize = @intFromPtr(array_buffer.ptr);
|
||||
// const Sizes = @import("../bindings/sizes.zig");
|
||||
// assert(addr == @intFromPtr(value.asEncoded().ptr) + Sizes.Bun_FFI_PointerOffsetToTypedArrayVector);
|
||||
|
||||
if (byteOffset) |off| {
|
||||
if (!off.isEmptyOrUndefinedOrNull()) {
|
||||
if (!off.isNumber()) {
|
||||
return JSC.toInvalidArguments("Expected number for byteOffset", .{}, globalThis);
|
||||
}
|
||||
}
|
||||
|
||||
const bytei64 = off.toInt64();
|
||||
if (bytei64 < 0) {
|
||||
addr -|= @as(usize, @intCast(bytei64 * -1));
|
||||
} else {
|
||||
addr += @as(usize, @intCast(bytei64));
|
||||
}
|
||||
|
||||
if (addr > @intFromPtr(array_buffer.ptr) + @as(usize, array_buffer.byte_len)) {
|
||||
return JSC.toInvalidArguments("byteOffset out of bounds", .{}, globalThis);
|
||||
}
|
||||
}
|
||||
|
||||
if (addr > max_addressable_memory) {
|
||||
return JSC.toInvalidArguments("Pointer is outside max addressible memory, which usually means a bug in your program.", .{}, globalThis);
|
||||
}
|
||||
|
||||
if (addr == 0) {
|
||||
return JSC.toInvalidArguments("Pointer must not be 0", .{}, globalThis);
|
||||
}
|
||||
|
||||
if (addr == 0xDEADBEEF or addr == 0xaaaaaaaa or addr == 0xAAAAAAAA) {
|
||||
return JSC.toInvalidArguments("ptr to invalid memory, that would segfault Bun :(", .{}, globalThis);
|
||||
}
|
||||
|
||||
if (comptime Environment.allow_assert) {
|
||||
assert(JSC.JSValue.fromPtrAddress(addr).asPtrAddress() == addr);
|
||||
}
|
||||
|
||||
return JSC.JSValue.fromPtrAddress(addr);
|
||||
}
|
||||
|
||||
const ValueOrError = union(enum) {
|
||||
err: JSValue,
|
||||
slice: []u8,
|
||||
};
|
||||
|
||||
pub fn getPtrSlice(globalThis: *JSGlobalObject, value: JSValue, byteOffset: ?JSValue, byteLength: ?JSValue) ValueOrError {
|
||||
if (!value.isNumber()) {
|
||||
return .{ .err = JSC.toInvalidArguments("ptr must be a number.", .{}, globalThis) };
|
||||
}
|
||||
|
||||
const num = value.asPtrAddress();
|
||||
if (num == 0) {
|
||||
return .{ .err = JSC.toInvalidArguments("ptr cannot be zero, that would segfault Bun :(", .{}, globalThis) };
|
||||
}
|
||||
|
||||
// if (!std.math.isFinite(num)) {
|
||||
// return .{ .err = JSC.toInvalidArguments("ptr must be a finite number.", .{}, globalThis) };
|
||||
// }
|
||||
|
||||
var addr = @as(usize, @bitCast(num));
|
||||
|
||||
if (byteOffset) |byte_off| {
|
||||
if (byte_off.isNumber()) {
|
||||
const off = byte_off.toInt64();
|
||||
if (off < 0) {
|
||||
addr -|= @as(usize, @intCast(off * -1));
|
||||
} else {
|
||||
addr +|= @as(usize, @intCast(off));
|
||||
}
|
||||
|
||||
if (addr == 0) {
|
||||
return .{ .err = JSC.toInvalidArguments("ptr cannot be zero, that would segfault Bun :(", .{}, globalThis) };
|
||||
}
|
||||
|
||||
if (!std.math.isFinite(byte_off.asNumber())) {
|
||||
return .{ .err = JSC.toInvalidArguments("ptr must be a finite number.", .{}, globalThis) };
|
||||
}
|
||||
} else if (!byte_off.isEmptyOrUndefinedOrNull()) {
|
||||
// do nothing
|
||||
} else {
|
||||
return .{ .err = JSC.toInvalidArguments("Expected number for byteOffset", .{}, globalThis) };
|
||||
}
|
||||
}
|
||||
|
||||
if (addr == 0xDEADBEEF or addr == 0xaaaaaaaa or addr == 0xAAAAAAAA) {
|
||||
return .{ .err = JSC.toInvalidArguments("ptr to invalid memory, that would segfault Bun :(", .{}, globalThis) };
|
||||
}
|
||||
|
||||
if (byteLength) |valueLength| {
|
||||
if (!valueLength.isEmptyOrUndefinedOrNull()) {
|
||||
if (!valueLength.isNumber()) {
|
||||
return .{ .err = JSC.toInvalidArguments("length must be a number.", .{}, globalThis) };
|
||||
}
|
||||
|
||||
if (valueLength.asNumber() == 0.0) {
|
||||
return .{ .err = JSC.toInvalidArguments("length must be > 0. This usually means a bug in your code.", .{}, globalThis) };
|
||||
}
|
||||
|
||||
const length_i = valueLength.toInt64();
|
||||
if (length_i < 0) {
|
||||
return .{ .err = JSC.toInvalidArguments("length must be > 0. This usually means a bug in your code.", .{}, globalThis) };
|
||||
}
|
||||
|
||||
if (length_i > max_addressable_memory) {
|
||||
return .{ .err = JSC.toInvalidArguments("length exceeds max addressable memory. This usually means a bug in your code.", .{}, globalThis) };
|
||||
}
|
||||
|
||||
const length = @as(usize, @intCast(length_i));
|
||||
return .{ .slice = @as([*]u8, @ptrFromInt(addr))[0..length] };
|
||||
}
|
||||
}
|
||||
|
||||
return .{ .slice = bun.span(@as([*:0]u8, @ptrFromInt(addr))) };
|
||||
}
|
||||
|
||||
fn getCPtr(value: JSValue) ?usize {
|
||||
// pointer to C function
|
||||
if (value.isNumber()) {
|
||||
const addr = value.asPtrAddress();
|
||||
if (addr > 0) return addr;
|
||||
} else if (value.isBigInt()) {
|
||||
const addr = @as(u64, @bitCast(value.toUInt64NoTruncate()));
|
||||
if (addr > 0) {
|
||||
return addr;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn toArrayBuffer(
|
||||
globalThis: *JSGlobalObject,
|
||||
value: JSValue,
|
||||
byteOffset: ?JSValue,
|
||||
valueLength: ?JSValue,
|
||||
finalizationCtxOrPtr: ?JSValue,
|
||||
finalizationCallback: ?JSValue,
|
||||
) JSC.JSValue {
|
||||
switch (getPtrSlice(globalThis, value, byteOffset, valueLength)) {
|
||||
.err => |erro| {
|
||||
return erro;
|
||||
},
|
||||
.slice => |slice| {
|
||||
var callback: JSC.C.JSTypedArrayBytesDeallocator = null;
|
||||
var ctx: ?*anyopaque = null;
|
||||
if (finalizationCallback) |callback_value| {
|
||||
if (getCPtr(callback_value)) |callback_ptr| {
|
||||
callback = @as(JSC.C.JSTypedArrayBytesDeallocator, @ptrFromInt(callback_ptr));
|
||||
|
||||
if (finalizationCtxOrPtr) |ctx_value| {
|
||||
if (getCPtr(ctx_value)) |ctx_ptr| {
|
||||
ctx = @as(*anyopaque, @ptrFromInt(ctx_ptr));
|
||||
} else if (!ctx_value.isUndefinedOrNull()) {
|
||||
return JSC.toInvalidArguments("Expected user data to be a C pointer (number or BigInt)", .{}, globalThis);
|
||||
}
|
||||
}
|
||||
} else if (!callback_value.isEmptyOrUndefinedOrNull()) {
|
||||
return JSC.toInvalidArguments("Expected callback to be a C pointer (number or BigInt)", .{}, globalThis);
|
||||
}
|
||||
} else if (finalizationCtxOrPtr) |callback_value| {
|
||||
if (getCPtr(callback_value)) |callback_ptr| {
|
||||
callback = @as(JSC.C.JSTypedArrayBytesDeallocator, @ptrFromInt(callback_ptr));
|
||||
} else if (!callback_value.isEmptyOrUndefinedOrNull()) {
|
||||
return JSC.toInvalidArguments("Expected callback to be a C pointer (number or BigInt)", .{}, globalThis);
|
||||
}
|
||||
}
|
||||
|
||||
return JSC.ArrayBuffer.fromBytes(slice, JSC.JSValue.JSType.ArrayBuffer).toJSWithContext(globalThis, ctx, callback, null);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toBuffer(
|
||||
globalThis: *JSGlobalObject,
|
||||
value: JSValue,
|
||||
byteOffset: ?JSValue,
|
||||
valueLength: ?JSValue,
|
||||
finalizationCtxOrPtr: ?JSValue,
|
||||
finalizationCallback: ?JSValue,
|
||||
) JSC.JSValue {
|
||||
switch (getPtrSlice(globalThis, value, byteOffset, valueLength)) {
|
||||
.err => |err| {
|
||||
return err;
|
||||
},
|
||||
.slice => |slice| {
|
||||
var callback: JSC.C.JSTypedArrayBytesDeallocator = null;
|
||||
var ctx: ?*anyopaque = null;
|
||||
if (finalizationCallback) |callback_value| {
|
||||
if (getCPtr(callback_value)) |callback_ptr| {
|
||||
callback = @as(JSC.C.JSTypedArrayBytesDeallocator, @ptrFromInt(callback_ptr));
|
||||
|
||||
if (finalizationCtxOrPtr) |ctx_value| {
|
||||
if (getCPtr(ctx_value)) |ctx_ptr| {
|
||||
ctx = @as(*anyopaque, @ptrFromInt(ctx_ptr));
|
||||
} else if (!ctx_value.isEmptyOrUndefinedOrNull()) {
|
||||
return JSC.toInvalidArguments("Expected user data to be a C pointer (number or BigInt)", .{}, globalThis);
|
||||
}
|
||||
}
|
||||
} else if (!callback_value.isEmptyOrUndefinedOrNull()) {
|
||||
return JSC.toInvalidArguments("Expected callback to be a C pointer (number or BigInt)", .{}, globalThis);
|
||||
}
|
||||
} else if (finalizationCtxOrPtr) |callback_value| {
|
||||
if (getCPtr(callback_value)) |callback_ptr| {
|
||||
callback = @as(JSC.C.JSTypedArrayBytesDeallocator, @ptrFromInt(callback_ptr));
|
||||
} else if (!callback_value.isEmptyOrUndefinedOrNull()) {
|
||||
return JSC.toInvalidArguments("Expected callback to be a C pointer (number or BigInt)", .{}, globalThis);
|
||||
}
|
||||
}
|
||||
|
||||
if (callback != null or ctx != null) {
|
||||
return JSC.JSValue.createBufferWithCtx(globalThis, slice, ctx, callback);
|
||||
}
|
||||
|
||||
return JSC.JSValue.createBuffer(globalThis, slice, null);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toCStringBuffer(
|
||||
globalThis: *JSGlobalObject,
|
||||
value: JSValue,
|
||||
byteOffset: ?JSValue,
|
||||
valueLength: ?JSValue,
|
||||
) JSC.JSValue {
|
||||
switch (getPtrSlice(globalThis, value, byteOffset, valueLength)) {
|
||||
.err => |err| {
|
||||
return err;
|
||||
},
|
||||
.slice => |slice| {
|
||||
return JSC.JSValue.createBuffer(globalThis, slice, null);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getter(
|
||||
globalObject: *JSC.JSGlobalObject,
|
||||
_: *JSC.JSObject,
|
||||
) JSC.JSValue {
|
||||
return FFIObject.toJS(globalObject);
|
||||
}
|
||||
|
||||
const fields = .{
|
||||
.viewSource = JSC.wrapStaticMethod(
|
||||
JSC.FFI,
|
||||
"print",
|
||||
false,
|
||||
),
|
||||
.dlopen = JSC.wrapStaticMethod(JSC.FFI, "open", false),
|
||||
.callback = JSC.wrapStaticMethod(JSC.FFI, "callback", false),
|
||||
.linkSymbols = JSC.wrapStaticMethod(JSC.FFI, "linkSymbols", false),
|
||||
.toBuffer = JSC.wrapStaticMethod(@This(), "toBuffer", false),
|
||||
.toArrayBuffer = JSC.wrapStaticMethod(@This(), "toArrayBuffer", false),
|
||||
.closeCallback = JSC.wrapStaticMethod(JSC.FFI, "closeCallback", false),
|
||||
.CString = JSC.wrapStaticMethod(Bun.FFIObject, "newCString", false),
|
||||
};
|
||||
const max_addressable_memory = std.math.maxInt(u56);
|
||||
|
||||
const JSGlobalObject = JSC.JSGlobalObject;
|
||||
const JSObject = JSC.JSObject;
|
||||
const JSValue = JSC.JSValue;
|
||||
const JSC = bun.JSC;
|
||||
const bun = @import("root").bun;
|
||||
const FFIObject = @This();
|
||||
const Bun = JSC.API.Bun;
|
||||
|
||||
const Environment = bun.Environment;
|
||||
const std = @import("std");
|
||||
const assert = bun.assert;
|
||||
const ZigString = JSC.ZigString;
|
||||
144
src/bun.js/api/HashObject.zig
Normal file
144
src/bun.js/api/HashObject.zig
Normal file
@@ -0,0 +1,144 @@
|
||||
pub const wyhash = hashWrap(std.hash.Wyhash);
|
||||
pub const adler32 = hashWrap(std.hash.Adler32);
|
||||
pub const crc32 = hashWrap(std.hash.Crc32);
|
||||
pub const cityHash32 = hashWrap(std.hash.CityHash32);
|
||||
pub const cityHash64 = hashWrap(std.hash.CityHash64);
|
||||
pub const xxHash32 = hashWrap(struct {
|
||||
pub fn hash(seed: u32, bytes: []const u8) u32 {
|
||||
// sidestep .hash taking in anytype breaking ArgTuple
|
||||
// downstream by forcing a type signature on the input
|
||||
return std.hash.XxHash32.hash(seed, bytes);
|
||||
}
|
||||
});
|
||||
pub const xxHash64 = hashWrap(struct {
|
||||
pub fn hash(seed: u32, bytes: []const u8) u64 {
|
||||
// sidestep .hash taking in anytype breaking ArgTuple
|
||||
// downstream by forcing a type signature on the input
|
||||
return std.hash.XxHash64.hash(seed, bytes);
|
||||
}
|
||||
});
|
||||
pub const xxHash3 = hashWrap(struct {
|
||||
pub fn hash(seed: u32, bytes: []const u8) u64 {
|
||||
// sidestep .hash taking in anytype breaking ArgTuple
|
||||
// downstream by forcing a type signature on the input
|
||||
return std.hash.XxHash3.hash(seed, bytes);
|
||||
}
|
||||
});
|
||||
pub const murmur32v2 = hashWrap(std.hash.murmur.Murmur2_32);
|
||||
pub const murmur32v3 = hashWrap(std.hash.murmur.Murmur3_32);
|
||||
pub const murmur64v2 = hashWrap(std.hash.murmur.Murmur2_64);
|
||||
|
||||
pub fn create(globalThis: *JSC.JSGlobalObject) JSC.JSValue {
|
||||
const function = JSC.createCallback(globalThis, ZigString.static("hash"), 1, wyhash);
|
||||
const fns = comptime .{
|
||||
"wyhash",
|
||||
"adler32",
|
||||
"crc32",
|
||||
"cityHash32",
|
||||
"cityHash64",
|
||||
"xxHash32",
|
||||
"xxHash64",
|
||||
"xxHash3",
|
||||
"murmur32v2",
|
||||
"murmur32v3",
|
||||
"murmur64v2",
|
||||
};
|
||||
inline for (fns) |name| {
|
||||
const value = JSC.createCallback(
|
||||
globalThis,
|
||||
ZigString.static(name),
|
||||
1,
|
||||
@field(HashObject, name),
|
||||
);
|
||||
function.put(globalThis, comptime ZigString.static(name), value);
|
||||
}
|
||||
|
||||
return function;
|
||||
}
|
||||
|
||||
fn hashWrap(comptime Hasher_: anytype) JSC.JSHostZigFunction {
|
||||
return struct {
|
||||
const Hasher = Hasher_;
|
||||
pub fn hash(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
||||
const arguments = callframe.arguments_old(2).slice();
|
||||
var args = JSC.Node.ArgumentsSlice.init(globalThis.bunVM(), arguments);
|
||||
defer args.deinit();
|
||||
|
||||
var input: []const u8 = "";
|
||||
var input_slice = ZigString.Slice.empty;
|
||||
defer input_slice.deinit();
|
||||
if (args.nextEat()) |arg| {
|
||||
if (arg.as(JSC.WebCore.Blob)) |blob| {
|
||||
// TODO: files
|
||||
input = blob.sharedView();
|
||||
} else {
|
||||
switch (arg.jsTypeLoose()) {
|
||||
.ArrayBuffer,
|
||||
.Int8Array,
|
||||
.Uint8Array,
|
||||
.Uint8ClampedArray,
|
||||
.Int16Array,
|
||||
.Uint16Array,
|
||||
.Int32Array,
|
||||
.Uint32Array,
|
||||
.Float16Array,
|
||||
.Float32Array,
|
||||
.Float64Array,
|
||||
.BigInt64Array,
|
||||
.BigUint64Array,
|
||||
.DataView,
|
||||
=> {
|
||||
var array_buffer = arg.asArrayBuffer(globalThis) orelse {
|
||||
return globalThis.throwInvalidArguments("ArrayBuffer conversion error", .{});
|
||||
};
|
||||
input = array_buffer.byteSlice();
|
||||
},
|
||||
else => {
|
||||
input_slice = try arg.toSlice(globalThis, bun.default_allocator);
|
||||
input = input_slice.slice();
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// std.hash has inconsistent interfaces
|
||||
//
|
||||
const Function = if (@hasDecl(Hasher, "hashWithSeed")) Hasher.hashWithSeed else Hasher.hash;
|
||||
var function_args: std.meta.ArgsTuple(@TypeOf(Function)) = undefined;
|
||||
if (comptime std.meta.fields(std.meta.ArgsTuple(@TypeOf(Function))).len == 1) {
|
||||
return JSC.JSValue.jsNumber(Function(input));
|
||||
} else {
|
||||
var seed: u64 = 0;
|
||||
if (args.nextEat()) |arg| {
|
||||
if (arg.isNumber() or arg.isBigInt()) {
|
||||
seed = arg.toUInt64NoTruncate();
|
||||
}
|
||||
}
|
||||
if (comptime bun.trait.isNumber(@TypeOf(function_args[0]))) {
|
||||
function_args[0] = @as(@TypeOf(function_args[0]), @truncate(seed));
|
||||
function_args[1] = input;
|
||||
} else {
|
||||
function_args[0] = input;
|
||||
function_args[1] = @as(@TypeOf(function_args[1]), @truncate(seed));
|
||||
}
|
||||
|
||||
const value = @call(.auto, Function, function_args);
|
||||
|
||||
if (@TypeOf(value) == u32) {
|
||||
return JSC.JSValue.jsNumber(@as(u32, @bitCast(value)));
|
||||
}
|
||||
return JSC.JSValue.fromUInt64NoTruncate(globalThis, value);
|
||||
}
|
||||
}
|
||||
}.hash;
|
||||
}
|
||||
|
||||
const HashObject = @This();
|
||||
|
||||
const JSC = bun.JSC;
|
||||
const JSValue = JSC.JSValue;
|
||||
const JSGlobalObject = JSC.JSGlobalObject;
|
||||
const JSObject = JSC.JSObject;
|
||||
const std = @import("std");
|
||||
const bun = @import("root").bun;
|
||||
const ZigString = JSC.ZigString;
|
||||
72
src/bun.js/api/TOMLObject.zig
Normal file
72
src/bun.js/api/TOMLObject.zig
Normal file
@@ -0,0 +1,72 @@
|
||||
pub fn create(globalThis: *JSC.JSGlobalObject) JSC.JSValue {
|
||||
const object = JSValue.createEmptyObject(globalThis, 1);
|
||||
object.put(
|
||||
globalThis,
|
||||
ZigString.static("parse"),
|
||||
JSC.createCallback(
|
||||
globalThis,
|
||||
ZigString.static("parse"),
|
||||
1,
|
||||
parse,
|
||||
),
|
||||
);
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
pub fn parse(
|
||||
globalThis: *JSC.JSGlobalObject,
|
||||
callframe: *JSC.CallFrame,
|
||||
) bun.JSError!JSC.JSValue {
|
||||
var arena = bun.ArenaAllocator.init(globalThis.allocator());
|
||||
const allocator = arena.allocator();
|
||||
defer arena.deinit();
|
||||
var log = logger.Log.init(default_allocator);
|
||||
const arguments = callframe.arguments_old(1).slice();
|
||||
if (arguments.len == 0 or arguments[0].isEmptyOrUndefinedOrNull()) {
|
||||
return globalThis.throwInvalidArguments("Expected a string to parse", .{});
|
||||
}
|
||||
|
||||
var input_slice = try arguments[0].toSlice(globalThis, bun.default_allocator);
|
||||
defer input_slice.deinit();
|
||||
var source = logger.Source.initPathString("input.toml", input_slice.slice());
|
||||
const parse_result = TOMLParser.parse(&source, &log, allocator, false) catch {
|
||||
return globalThis.throwValue(log.toJS(globalThis, default_allocator, "Failed to parse toml"));
|
||||
};
|
||||
|
||||
// for now...
|
||||
const buffer_writer = js_printer.BufferWriter.init(allocator) catch {
|
||||
return globalThis.throwValue(log.toJS(globalThis, default_allocator, "Failed to print toml"));
|
||||
};
|
||||
var writer = js_printer.BufferPrinter.init(buffer_writer);
|
||||
_ = js_printer.printJSON(
|
||||
*js_printer.BufferPrinter,
|
||||
&writer,
|
||||
parse_result,
|
||||
&source,
|
||||
.{
|
||||
.mangled_props = null,
|
||||
},
|
||||
) catch {
|
||||
return globalThis.throwValue(log.toJS(globalThis, default_allocator, "Failed to print toml"));
|
||||
};
|
||||
|
||||
const slice = writer.ctx.buffer.slice();
|
||||
var out = bun.String.fromUTF8(slice);
|
||||
defer out.deref();
|
||||
|
||||
return out.toJSByParseJSON(globalThis);
|
||||
}
|
||||
|
||||
const TOMLObject = @This();
|
||||
const JSC = bun.JSC;
|
||||
const JSValue = JSC.JSValue;
|
||||
const JSGlobalObject = JSC.JSGlobalObject;
|
||||
const JSObject = JSC.JSObject;
|
||||
const std = @import("std");
|
||||
const ZigString = JSC.ZigString;
|
||||
const logger = bun.logger;
|
||||
const bun = @import("root").bun;
|
||||
const js_printer = bun.js_printer;
|
||||
const default_allocator = bun.default_allocator;
|
||||
const TOMLParser = @import("../../toml/toml_parser.zig").TOML;
|
||||
76
src/bun.js/api/UnsafeObject.zig
Normal file
76
src/bun.js/api/UnsafeObject.zig
Normal file
@@ -0,0 +1,76 @@
|
||||
pub fn create(globalThis: *JSC.JSGlobalObject) JSC.JSValue {
|
||||
const object = JSValue.createEmptyObject(globalThis, 3);
|
||||
const fields = comptime .{
|
||||
.gcAggressionLevel = gcAggressionLevel,
|
||||
.arrayBufferToString = arrayBufferToString,
|
||||
.mimallocDump = dump_mimalloc,
|
||||
};
|
||||
inline for (comptime std.meta.fieldNames(@TypeOf(fields))) |name| {
|
||||
object.put(
|
||||
globalThis,
|
||||
comptime ZigString.static(name),
|
||||
JSC.createCallback(globalThis, comptime ZigString.static(name), 1, comptime @field(fields, name)),
|
||||
);
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
pub fn gcAggressionLevel(
|
||||
globalThis: *JSC.JSGlobalObject,
|
||||
callframe: *JSC.CallFrame,
|
||||
) bun.JSError!JSC.JSValue {
|
||||
const ret = JSValue.jsNumber(@as(i32, @intFromEnum(globalThis.bunVM().aggressive_garbage_collection)));
|
||||
const value = callframe.arguments_old(1).ptr[0];
|
||||
|
||||
if (!value.isEmptyOrUndefinedOrNull()) {
|
||||
switch (value.coerce(i32, globalThis)) {
|
||||
1 => globalThis.bunVM().aggressive_garbage_collection = .mild,
|
||||
2 => globalThis.bunVM().aggressive_garbage_collection = .aggressive,
|
||||
0 => globalThis.bunVM().aggressive_garbage_collection = .none,
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
pub fn arrayBufferToString(
|
||||
globalThis: *JSC.JSGlobalObject,
|
||||
callframe: *JSC.CallFrame,
|
||||
) bun.JSError!JSC.JSValue {
|
||||
const args = callframe.arguments_old(2).slice();
|
||||
if (args.len < 1 or !args[0].isCell() or !args[0].jsType().isTypedArrayOrArrayBuffer()) {
|
||||
return globalThis.throwInvalidArguments("Expected an ArrayBuffer", .{});
|
||||
}
|
||||
|
||||
const array_buffer = JSC.ArrayBuffer.fromTypedArray(globalThis, args[0]);
|
||||
switch (array_buffer.typed_array_type) {
|
||||
.Uint16Array, .Int16Array => {
|
||||
var zig_str = ZigString.init("");
|
||||
zig_str._unsafe_ptr_do_not_use = @as([*]const u8, @ptrCast(@alignCast(array_buffer.ptr)));
|
||||
zig_str.len = array_buffer.len;
|
||||
zig_str.markUTF16();
|
||||
return zig_str.toJS(globalThis);
|
||||
},
|
||||
else => {
|
||||
return ZigString.init(array_buffer.slice()).toJS(globalThis);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
extern fn dump_zone_malloc_stats() void;
|
||||
|
||||
fn dump_mimalloc(globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
||||
globalObject.bunVM().arena.dumpStats();
|
||||
if (bun.heap_breakdown.enabled) {
|
||||
dump_zone_malloc_stats();
|
||||
}
|
||||
return .undefined;
|
||||
}
|
||||
|
||||
const JSC = bun.JSC;
|
||||
const JSValue = JSC.JSValue;
|
||||
const JSGlobalObject = JSC.JSGlobalObject;
|
||||
const JSObject = JSC.JSObject;
|
||||
const std = @import("std");
|
||||
const bun = @import("root").bun;
|
||||
const ZigString = JSC.ZigString;
|
||||
32
src/bun.js/api/crypto.zig
Normal file
32
src/bun.js/api/crypto.zig
Normal file
@@ -0,0 +1,32 @@
|
||||
pub fn createCryptoError(globalThis: *JSC.JSGlobalObject, err_code: u32) JSValue {
|
||||
return bun.BoringSSL.ERR_toJS(globalThis, err_code);
|
||||
}
|
||||
|
||||
pub const PasswordObject = @import("./crypto/PasswordObject.zig").PasswordObject;
|
||||
pub const JSPasswordObject = @import("./crypto/PasswordObject.zig").JSPasswordObject;
|
||||
|
||||
pub const CryptoHasher = @import("./crypto/CryptoHasher.zig").CryptoHasher;
|
||||
pub const MD4 = @import("./crypto/CryptoHasher.zig").MD4;
|
||||
pub const MD5 = @import("./crypto/CryptoHasher.zig").MD5;
|
||||
pub const SHA1 = @import("./crypto/CryptoHasher.zig").SHA1;
|
||||
pub const SHA224 = @import("./crypto/CryptoHasher.zig").SHA224;
|
||||
pub const SHA256 = @import("./crypto/CryptoHasher.zig").SHA256;
|
||||
pub const SHA384 = @import("./crypto/CryptoHasher.zig").SHA384;
|
||||
pub const SHA512 = @import("./crypto/CryptoHasher.zig").SHA512;
|
||||
pub const SHA512_256 = @import("./crypto/CryptoHasher.zig").SHA512_256;
|
||||
pub const HMAC = @import("./crypto/HMAC.zig");
|
||||
pub const EVP = @import("./crypto/EVP.zig");
|
||||
|
||||
comptime {
|
||||
CryptoHasher.Extern.@"export"();
|
||||
}
|
||||
|
||||
const std = @import("std");
|
||||
const bun = @import("root").bun;
|
||||
const string = bun.string;
|
||||
const strings = bun.strings;
|
||||
const MutableString = bun.MutableString;
|
||||
const stringZ = bun.stringZ;
|
||||
const JSC = bun.JSC;
|
||||
const JSValue = JSC.JSValue;
|
||||
const JSGlobalObject = JSC.JSGlobalObject;
|
||||
898
src/bun.js/api/crypto/CryptoHasher.zig
Normal file
898
src/bun.js/api/crypto/CryptoHasher.zig
Normal file
@@ -0,0 +1,898 @@
|
||||
pub const CryptoHasher = union(enum) {
|
||||
// HMAC_CTX contains 3 EVP_CTX, so let's store it as a pointer.
|
||||
hmac: ?*HMAC,
|
||||
|
||||
evp: EVP,
|
||||
zig: CryptoHasherZig,
|
||||
|
||||
const Digest = EVP.Digest;
|
||||
|
||||
pub usingnamespace JSC.Codegen.JSCryptoHasher;
|
||||
usingnamespace bun.New(@This());
|
||||
|
||||
// For using only CryptoHasherZig in c++
|
||||
pub const Extern = struct {
|
||||
fn getByName(global: *JSGlobalObject, name_bytes: [*:0]const u8, name_len: usize) callconv(.C) ?*CryptoHasher {
|
||||
const name = name_bytes[0..name_len];
|
||||
|
||||
if (CryptoHasherZig.init(name)) |inner| {
|
||||
return CryptoHasher.new(.{
|
||||
.zig = inner,
|
||||
});
|
||||
}
|
||||
|
||||
const algorithm = EVP.Algorithm.map.get(name) orelse {
|
||||
return null;
|
||||
};
|
||||
|
||||
switch (algorithm) {
|
||||
.ripemd160,
|
||||
.blake2b256,
|
||||
.blake2b512,
|
||||
|
||||
.@"sha512-224",
|
||||
=> {
|
||||
if (algorithm.md()) |md| {
|
||||
return CryptoHasher.new(.{
|
||||
.evp = EVP.init(algorithm, md, global.bunVM().rareData().boringEngine()),
|
||||
});
|
||||
}
|
||||
},
|
||||
else => {
|
||||
return null;
|
||||
},
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
fn getFromOther(global: *JSGlobalObject, other_handle: *CryptoHasher) callconv(.C) ?*CryptoHasher {
|
||||
switch (other_handle.*) {
|
||||
.zig => |other| {
|
||||
const hasher = CryptoHasher.new(.{
|
||||
.zig = other.copy(),
|
||||
});
|
||||
return hasher;
|
||||
},
|
||||
.evp => |other| {
|
||||
return CryptoHasher.new(.{
|
||||
.evp = other.copy(global.bunVM().rareData().boringEngine()) catch {
|
||||
return null;
|
||||
},
|
||||
});
|
||||
},
|
||||
else => {
|
||||
return null;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn destroy(handle: *CryptoHasher) callconv(.C) void {
|
||||
handle.finalize();
|
||||
}
|
||||
|
||||
fn update(handle: *CryptoHasher, input_bytes: [*]const u8, input_len: usize) callconv(.C) bool {
|
||||
const input = input_bytes[0..input_len];
|
||||
|
||||
switch (handle.*) {
|
||||
.zig => {
|
||||
handle.zig.update(input);
|
||||
return true;
|
||||
},
|
||||
.evp => {
|
||||
handle.evp.update(input);
|
||||
return true;
|
||||
},
|
||||
else => {
|
||||
return false;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn digest(handle: *CryptoHasher, global: *JSGlobalObject, buf: [*]u8, buf_len: usize) callconv(.C) u32 {
|
||||
const digest_buf = buf[0..buf_len];
|
||||
switch (handle.*) {
|
||||
.zig => {
|
||||
const res = handle.zig.finalWithLen(digest_buf, buf_len);
|
||||
return @intCast(res.len);
|
||||
},
|
||||
.evp => {
|
||||
const res = handle.evp.final(global.bunVM().rareData().boringEngine(), digest_buf);
|
||||
return @intCast(res.len);
|
||||
},
|
||||
else => {
|
||||
return 0;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn getDigestSize(handle: *CryptoHasher) callconv(.C) u32 {
|
||||
return switch (handle.*) {
|
||||
.zig => |inner| inner.digest_length,
|
||||
.evp => |inner| inner.size(),
|
||||
else => 0,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn @"export"() void {
|
||||
@export(&CryptoHasher.Extern.getByName, .{ .name = "Bun__CryptoHasherExtern__getByName" });
|
||||
@export(&CryptoHasher.Extern.getFromOther, .{ .name = "Bun__CryptoHasherExtern__getFromOther" });
|
||||
@export(&CryptoHasher.Extern.destroy, .{ .name = "Bun__CryptoHasherExtern__destroy" });
|
||||
@export(&CryptoHasher.Extern.update, .{ .name = "Bun__CryptoHasherExtern__update" });
|
||||
@export(&CryptoHasher.Extern.digest, .{ .name = "Bun__CryptoHasherExtern__digest" });
|
||||
@export(&CryptoHasher.Extern.getDigestSize, .{ .name = "Bun__CryptoHasherExtern__getDigestSize" });
|
||||
}
|
||||
};
|
||||
|
||||
pub const digest = JSC.wrapInstanceMethod(CryptoHasher, "digest_", false);
|
||||
pub const hash = JSC.wrapStaticMethod(CryptoHasher, "hash_", false);
|
||||
|
||||
fn throwHmacConsumed(globalThis: *JSC.JSGlobalObject) bun.JSError {
|
||||
return globalThis.throw("HMAC has been consumed and is no longer usable", .{});
|
||||
}
|
||||
|
||||
pub fn getByteLength(this: *CryptoHasher, globalThis: *JSC.JSGlobalObject) JSC.JSValue {
|
||||
return JSC.JSValue.jsNumber(switch (this.*) {
|
||||
.evp => |*inner| inner.size(),
|
||||
.hmac => |inner| if (inner) |hmac| hmac.size() else {
|
||||
throwHmacConsumed(globalThis) catch return .zero;
|
||||
},
|
||||
.zig => |*inner| inner.digest_length,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn getAlgorithm(this: *CryptoHasher, globalObject: *JSC.JSGlobalObject) JSC.JSValue {
|
||||
return switch (this.*) {
|
||||
inline .evp, .zig => |*inner| ZigString.fromUTF8(bun.asByteSlice(@tagName(inner.algorithm))).toJS(globalObject),
|
||||
.hmac => |inner| if (inner) |hmac| ZigString.fromUTF8(bun.asByteSlice(@tagName(hmac.algorithm))).toJS(globalObject) else {
|
||||
throwHmacConsumed(globalObject) catch return .zero;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getAlgorithms(
|
||||
globalThis_: *JSC.JSGlobalObject,
|
||||
_: JSValue,
|
||||
_: JSValue,
|
||||
) JSC.JSValue {
|
||||
return bun.String.toJSArray(globalThis_, &EVP.Algorithm.names.values);
|
||||
}
|
||||
|
||||
fn hashToEncoding(globalThis: *JSGlobalObject, evp: *EVP, input: JSC.Node.BlobOrStringOrBuffer, encoding: JSC.Node.Encoding) bun.JSError!JSC.JSValue {
|
||||
var output_digest_buf: Digest = undefined;
|
||||
defer input.deinit();
|
||||
|
||||
if (input == .blob and input.blob.isBunFile()) {
|
||||
return globalThis.throw("Bun.file() is not supported here yet (it needs an async version)", .{});
|
||||
}
|
||||
|
||||
const len = evp.hash(globalThis.bunVM().rareData().boringEngine(), input.slice(), &output_digest_buf) orelse {
|
||||
const err = BoringSSL.ERR_get_error();
|
||||
const instance = createCryptoError(globalThis, err);
|
||||
BoringSSL.ERR_clear_error();
|
||||
return globalThis.throwValue(instance);
|
||||
};
|
||||
return encoding.encodeWithMaxSize(globalThis, BoringSSL.EVP_MAX_MD_SIZE, output_digest_buf[0..len]);
|
||||
}
|
||||
|
||||
fn hashToBytes(globalThis: *JSGlobalObject, evp: *EVP, input: JSC.Node.BlobOrStringOrBuffer, output: ?JSC.ArrayBuffer) bun.JSError!JSC.JSValue {
|
||||
var output_digest_buf: Digest = undefined;
|
||||
var output_digest_slice: []u8 = &output_digest_buf;
|
||||
defer input.deinit();
|
||||
|
||||
if (input == .blob and input.blob.isBunFile()) {
|
||||
return globalThis.throw("Bun.file() is not supported here yet (it needs an async version)", .{});
|
||||
}
|
||||
|
||||
if (output) |output_buf| {
|
||||
const size = evp.size();
|
||||
var bytes = output_buf.byteSlice();
|
||||
if (bytes.len < size) {
|
||||
return globalThis.throwInvalidArguments("TypedArray must be at least {d} bytes", .{size});
|
||||
}
|
||||
output_digest_slice = bytes[0..size];
|
||||
}
|
||||
|
||||
const len = evp.hash(globalThis.bunVM().rareData().boringEngine(), input.slice(), output_digest_slice) orelse {
|
||||
const err = BoringSSL.ERR_get_error();
|
||||
const instance = createCryptoError(globalThis, err);
|
||||
BoringSSL.ERR_clear_error();
|
||||
return globalThis.throwValue(instance);
|
||||
};
|
||||
|
||||
if (output) |output_buf| {
|
||||
return output_buf.value;
|
||||
} else {
|
||||
// Clone to GC-managed memory
|
||||
return JSC.ArrayBuffer.createBuffer(globalThis, output_digest_slice[0..len]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hash_(
|
||||
globalThis: *JSGlobalObject,
|
||||
algorithm: ZigString,
|
||||
input: JSC.Node.BlobOrStringOrBuffer,
|
||||
output: ?JSC.Node.StringOrBuffer,
|
||||
) bun.JSError!JSC.JSValue {
|
||||
var evp = EVP.byName(algorithm, globalThis) orelse return try CryptoHasherZig.hashByName(globalThis, algorithm, input, output) orelse {
|
||||
return globalThis.throwInvalidArguments("Unsupported algorithm \"{any}\"", .{algorithm});
|
||||
};
|
||||
defer evp.deinit();
|
||||
|
||||
if (output) |string_or_buffer| {
|
||||
switch (string_or_buffer) {
|
||||
inline else => |*str| {
|
||||
defer str.deinit();
|
||||
const encoding = JSC.Node.Encoding.from(str.slice()) orelse {
|
||||
return globalThis.ERR_INVALID_ARG_VALUE("Unknown encoding: {s}", .{str.slice()}).throw();
|
||||
};
|
||||
|
||||
return hashToEncoding(globalThis, &evp, input, encoding);
|
||||
},
|
||||
.buffer => |buffer| {
|
||||
return hashToBytes(globalThis, &evp, input, buffer.buffer);
|
||||
},
|
||||
}
|
||||
} else {
|
||||
return hashToBytes(globalThis, &evp, input, null);
|
||||
}
|
||||
}
|
||||
|
||||
// Bun.CryptoHasher(algorithm, hmacKey?: string | Buffer)
|
||||
pub fn constructor(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!*CryptoHasher {
|
||||
const arguments = callframe.arguments_old(2);
|
||||
if (arguments.len == 0) {
|
||||
return globalThis.throwInvalidArguments("Expected an algorithm name as an argument", .{});
|
||||
}
|
||||
|
||||
const algorithm_name = arguments.ptr[0];
|
||||
if (algorithm_name.isEmptyOrUndefinedOrNull() or !algorithm_name.isString()) {
|
||||
return globalThis.throwInvalidArguments("algorithm must be a string", .{});
|
||||
}
|
||||
|
||||
const algorithm = try algorithm_name.getZigString(globalThis);
|
||||
|
||||
if (algorithm.len == 0) {
|
||||
return globalThis.throwInvalidArguments("Invalid algorithm name", .{});
|
||||
}
|
||||
|
||||
const hmac_value = arguments.ptr[1];
|
||||
var hmac_key: ?JSC.Node.StringOrBuffer = null;
|
||||
defer {
|
||||
if (hmac_key) |*key| {
|
||||
key.deinit();
|
||||
}
|
||||
}
|
||||
|
||||
if (!hmac_value.isEmptyOrUndefinedOrNull()) {
|
||||
hmac_key = try JSC.Node.StringOrBuffer.fromJS(globalThis, bun.default_allocator, hmac_value) orelse {
|
||||
return globalThis.throwInvalidArguments("key must be a string or buffer", .{});
|
||||
};
|
||||
}
|
||||
|
||||
return CryptoHasher.new(brk: {
|
||||
if (hmac_key) |*key| {
|
||||
const chosen_algorithm = try algorithm_name.toEnumFromMap(globalThis, "algorithm", EVP.Algorithm, EVP.Algorithm.map);
|
||||
if (chosen_algorithm == .ripemd160) {
|
||||
// crashes at runtime.
|
||||
return globalThis.throw("ripemd160 is not supported", .{});
|
||||
}
|
||||
|
||||
break :brk .{
|
||||
.hmac = HMAC.init(chosen_algorithm, key.slice()) orelse {
|
||||
if (!globalThis.hasException()) {
|
||||
const err = BoringSSL.ERR_get_error();
|
||||
if (err != 0) {
|
||||
const instance = createCryptoError(globalThis, err);
|
||||
BoringSSL.ERR_clear_error();
|
||||
return globalThis.throwValue(instance);
|
||||
} else {
|
||||
return globalThis.throwTODO("HMAC is not supported for this algorithm yet");
|
||||
}
|
||||
}
|
||||
return error.JSError;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
break :brk .{
|
||||
.evp = EVP.byName(algorithm, globalThis) orelse return CryptoHasherZig.constructor(algorithm) orelse {
|
||||
return globalThis.throwInvalidArguments("Unsupported algorithm {any}", .{algorithm});
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
pub fn getter(
|
||||
globalObject: *JSC.JSGlobalObject,
|
||||
_: *JSC.JSObject,
|
||||
) JSC.JSValue {
|
||||
return CryptoHasher.getConstructor(globalObject);
|
||||
}
|
||||
|
||||
pub fn update(this: *CryptoHasher, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
||||
const thisValue = callframe.this();
|
||||
const arguments = callframe.arguments_old(2);
|
||||
const input = arguments.ptr[0];
|
||||
if (input.isEmptyOrUndefinedOrNull()) {
|
||||
return globalThis.throwInvalidArguments("expected blob, string or buffer", .{});
|
||||
}
|
||||
const encoding = arguments.ptr[1];
|
||||
const buffer = try JSC.Node.BlobOrStringOrBuffer.fromJSWithEncodingValue(globalThis, globalThis.bunVM().allocator, input, encoding) orelse {
|
||||
if (!globalThis.hasException()) return globalThis.throwInvalidArguments("expected blob, string or buffer", .{});
|
||||
return error.JSError;
|
||||
};
|
||||
defer buffer.deinit();
|
||||
if (buffer == .blob and buffer.blob.isBunFile()) {
|
||||
return globalThis.throw("Bun.file() is not supported here yet (it needs an async version)", .{});
|
||||
}
|
||||
|
||||
switch (this.*) {
|
||||
.evp => |*inner| {
|
||||
inner.update(buffer.slice());
|
||||
const err = BoringSSL.ERR_get_error();
|
||||
if (err != 0) {
|
||||
const instance = createCryptoError(globalThis, err);
|
||||
BoringSSL.ERR_clear_error();
|
||||
return globalThis.throwValue(instance);
|
||||
}
|
||||
},
|
||||
.hmac => |inner| {
|
||||
const hmac = inner orelse {
|
||||
return throwHmacConsumed(globalThis);
|
||||
};
|
||||
|
||||
hmac.update(buffer.slice());
|
||||
const err = BoringSSL.ERR_get_error();
|
||||
if (err != 0) {
|
||||
const instance = createCryptoError(globalThis, err);
|
||||
BoringSSL.ERR_clear_error();
|
||||
return globalThis.throwValue(instance);
|
||||
}
|
||||
},
|
||||
.zig => |*inner| {
|
||||
inner.update(buffer.slice());
|
||||
return thisValue;
|
||||
},
|
||||
}
|
||||
|
||||
return thisValue;
|
||||
}
|
||||
|
||||
pub fn copy(
|
||||
this: *CryptoHasher,
|
||||
globalObject: *JSC.JSGlobalObject,
|
||||
_: *JSC.CallFrame,
|
||||
) bun.JSError!JSC.JSValue {
|
||||
var new: CryptoHasher = undefined;
|
||||
switch (this.*) {
|
||||
.evp => |*inner| {
|
||||
new = .{ .evp = inner.copy(globalObject.bunVM().rareData().boringEngine()) catch bun.outOfMemory() };
|
||||
},
|
||||
.hmac => |inner| {
|
||||
const hmac = inner orelse {
|
||||
return throwHmacConsumed(globalObject);
|
||||
};
|
||||
new = .{
|
||||
.hmac = hmac.copy() catch {
|
||||
const err = createCryptoError(globalObject, BoringSSL.ERR_get_error());
|
||||
BoringSSL.ERR_clear_error();
|
||||
return globalObject.throwValue(err);
|
||||
},
|
||||
};
|
||||
},
|
||||
.zig => |*inner| {
|
||||
new = .{ .zig = inner.copy() };
|
||||
},
|
||||
}
|
||||
return CryptoHasher.new(new).toJS(globalObject);
|
||||
}
|
||||
|
||||
pub fn digest_(this: *CryptoHasher, globalThis: *JSGlobalObject, output: ?JSC.Node.StringOrBuffer) bun.JSError!JSC.JSValue {
|
||||
if (output) |string_or_buffer| {
|
||||
switch (string_or_buffer) {
|
||||
inline else => |*str| {
|
||||
defer str.deinit();
|
||||
const encoding = JSC.Node.Encoding.from(str.slice()) orelse {
|
||||
return globalThis.ERR_INVALID_ARG_VALUE("Unknown encoding: {s}", .{str.slice()}).throw();
|
||||
};
|
||||
|
||||
return this.digestToEncoding(globalThis, encoding);
|
||||
},
|
||||
.buffer => |buffer| {
|
||||
return this.digestToBytes(
|
||||
globalThis,
|
||||
buffer.buffer,
|
||||
);
|
||||
},
|
||||
}
|
||||
} else {
|
||||
return this.digestToBytes(globalThis, null);
|
||||
}
|
||||
}
|
||||
|
||||
fn digestToBytes(this: *CryptoHasher, globalThis: *JSGlobalObject, output: ?JSC.ArrayBuffer) bun.JSError!JSC.JSValue {
|
||||
var output_digest_buf: EVP.Digest = undefined;
|
||||
var output_digest_slice: []u8 = &output_digest_buf;
|
||||
if (output) |output_buf| {
|
||||
var bytes = output_buf.byteSlice();
|
||||
if (bytes.len < output_digest_buf.len) {
|
||||
return globalThis.throwInvalidArguments(comptime std.fmt.comptimePrint("TypedArray must be at least {d} bytes", .{output_digest_buf.len}), .{});
|
||||
}
|
||||
output_digest_slice = bytes[0..bytes.len];
|
||||
} else {
|
||||
output_digest_buf = std.mem.zeroes(EVP.Digest);
|
||||
}
|
||||
|
||||
const result = this.final(globalThis, output_digest_slice) catch return .zero;
|
||||
if (globalThis.hasException()) {
|
||||
return error.JSError;
|
||||
}
|
||||
|
||||
if (output) |output_buf| {
|
||||
return output_buf.value;
|
||||
} else {
|
||||
// Clone to GC-managed memory
|
||||
return JSC.ArrayBuffer.createBuffer(globalThis, result);
|
||||
}
|
||||
}
|
||||
|
||||
fn digestToEncoding(this: *CryptoHasher, globalThis: *JSGlobalObject, encoding: JSC.Node.Encoding) bun.JSError!JSC.JSValue {
|
||||
var output_digest_buf: EVP.Digest = std.mem.zeroes(EVP.Digest);
|
||||
const output_digest_slice: []u8 = &output_digest_buf;
|
||||
const out = this.final(globalThis, output_digest_slice) catch return .zero;
|
||||
if (globalThis.hasException()) {
|
||||
return error.JSError;
|
||||
}
|
||||
return encoding.encodeWithMaxSize(globalThis, BoringSSL.EVP_MAX_MD_SIZE, out);
|
||||
}
|
||||
|
||||
fn final(this: *CryptoHasher, globalThis: *JSGlobalObject, output_digest_slice: []u8) bun.JSError![]u8 {
|
||||
return switch (this.*) {
|
||||
.hmac => |inner| brk: {
|
||||
const hmac: *HMAC = inner orelse {
|
||||
return throwHmacConsumed(globalThis);
|
||||
};
|
||||
this.hmac = null;
|
||||
defer hmac.deinit();
|
||||
break :brk hmac.final(output_digest_slice);
|
||||
},
|
||||
.evp => |*inner| inner.final(globalThis.bunVM().rareData().boringEngine(), output_digest_slice),
|
||||
.zig => |*inner| inner.final(output_digest_slice),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn finalize(this: *CryptoHasher) void {
|
||||
switch (this.*) {
|
||||
.evp => |*inner| {
|
||||
// https://github.com/oven-sh/bun/issues/3250
|
||||
inner.deinit();
|
||||
},
|
||||
.zig => |*inner| {
|
||||
inner.deinit();
|
||||
},
|
||||
.hmac => |inner| {
|
||||
if (inner) |hmac| {
|
||||
hmac.deinit();
|
||||
}
|
||||
},
|
||||
}
|
||||
this.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
const CryptoHasherZig = struct {
|
||||
algorithm: EVP.Algorithm,
|
||||
state: *anyopaque,
|
||||
digest_length: u8,
|
||||
|
||||
const algo_map = [_]struct { string, type }{
|
||||
.{ "sha3-224", std.crypto.hash.sha3.Sha3_224 },
|
||||
.{ "sha3-256", std.crypto.hash.sha3.Sha3_256 },
|
||||
.{ "sha3-384", std.crypto.hash.sha3.Sha3_384 },
|
||||
.{ "sha3-512", std.crypto.hash.sha3.Sha3_512 },
|
||||
.{ "shake128", std.crypto.hash.sha3.Shake128 },
|
||||
.{ "shake256", std.crypto.hash.sha3.Shake256 },
|
||||
};
|
||||
|
||||
inline fn digestLength(Algorithm: type) comptime_int {
|
||||
return switch (Algorithm) {
|
||||
std.crypto.hash.sha3.Shake128 => 16,
|
||||
std.crypto.hash.sha3.Shake256 => 32,
|
||||
else => Algorithm.digest_length,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn hashByName(globalThis: *JSGlobalObject, algorithm: ZigString, input: JSC.Node.BlobOrStringOrBuffer, output: ?JSC.Node.StringOrBuffer) bun.JSError!?JSC.JSValue {
|
||||
inline for (algo_map) |item| {
|
||||
if (bun.strings.eqlComptime(algorithm.slice(), item[0])) {
|
||||
return try hashByNameInner(globalThis, item[1], input, output);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn hashByNameInner(globalThis: *JSGlobalObject, comptime Algorithm: type, input: JSC.Node.BlobOrStringOrBuffer, output: ?JSC.Node.StringOrBuffer) bun.JSError!JSC.JSValue {
|
||||
if (output) |string_or_buffer| {
|
||||
switch (string_or_buffer) {
|
||||
inline else => |*str| {
|
||||
defer str.deinit();
|
||||
const encoding = JSC.Node.Encoding.from(str.slice()) orelse {
|
||||
return globalThis.ERR_INVALID_ARG_VALUE("Unknown encoding: {s}", .{str.slice()}).throw();
|
||||
};
|
||||
|
||||
if (encoding == .buffer) {
|
||||
return hashByNameInnerToBytes(globalThis, Algorithm, input, null);
|
||||
}
|
||||
|
||||
return hashByNameInnerToString(globalThis, Algorithm, input, encoding);
|
||||
},
|
||||
.buffer => |buffer| {
|
||||
return hashByNameInnerToBytes(globalThis, Algorithm, input, buffer.buffer);
|
||||
},
|
||||
}
|
||||
}
|
||||
return hashByNameInnerToBytes(globalThis, Algorithm, input, null);
|
||||
}
|
||||
|
||||
fn hashByNameInnerToString(globalThis: *JSGlobalObject, comptime Algorithm: type, input: JSC.Node.BlobOrStringOrBuffer, encoding: JSC.Node.Encoding) bun.JSError!JSC.JSValue {
|
||||
defer input.deinit();
|
||||
|
||||
if (input == .blob and input.blob.isBunFile()) {
|
||||
return globalThis.throw("Bun.file() is not supported here yet (it needs an async version)", .{});
|
||||
}
|
||||
|
||||
var h = Algorithm.init(.{});
|
||||
h.update(input.slice());
|
||||
|
||||
var out: [digestLength(Algorithm)]u8 = undefined;
|
||||
h.final(&out);
|
||||
|
||||
return encoding.encodeWithSize(globalThis, digestLength(Algorithm), &out);
|
||||
}
|
||||
|
||||
fn hashByNameInnerToBytes(globalThis: *JSGlobalObject, comptime Algorithm: type, input: JSC.Node.BlobOrStringOrBuffer, output: ?JSC.ArrayBuffer) bun.JSError!JSC.JSValue {
|
||||
defer input.deinit();
|
||||
|
||||
if (input == .blob and input.blob.isBunFile()) {
|
||||
return globalThis.throw("Bun.file() is not supported here yet (it needs an async version)", .{});
|
||||
}
|
||||
|
||||
var h = Algorithm.init(.{});
|
||||
const digest_length_comptime = digestLength(Algorithm);
|
||||
|
||||
if (output) |output_buf| {
|
||||
if (output_buf.byteSlice().len < digest_length_comptime) {
|
||||
return globalThis.throwInvalidArguments("TypedArray must be at least {d} bytes", .{digest_length_comptime});
|
||||
}
|
||||
}
|
||||
|
||||
h.update(input.slice());
|
||||
|
||||
if (output) |output_buf| {
|
||||
h.final(output_buf.slice()[0..digest_length_comptime]);
|
||||
return output_buf.value;
|
||||
} else {
|
||||
var out: [digestLength(Algorithm)]u8 = undefined;
|
||||
h.final(&out);
|
||||
// Clone to GC-managed memory
|
||||
return JSC.ArrayBuffer.createBuffer(globalThis, &out);
|
||||
}
|
||||
}
|
||||
|
||||
fn constructor(algorithm: ZigString) ?*CryptoHasher {
|
||||
inline for (algo_map) |item| {
|
||||
if (bun.strings.eqlComptime(algorithm.slice(), item[0])) {
|
||||
return CryptoHasher.new(.{ .zig = .{
|
||||
.algorithm = @field(EVP.Algorithm, item[0]),
|
||||
.state = bun.new(item[1], item[1].init(.{})),
|
||||
.digest_length = digestLength(item[1]),
|
||||
} });
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn init(algorithm: []const u8) ?CryptoHasherZig {
|
||||
inline for (algo_map) |item| {
|
||||
const name, const T = item;
|
||||
if (bun.strings.eqlComptime(algorithm, name)) {
|
||||
const handle: CryptoHasherZig = .{
|
||||
.algorithm = @field(EVP.Algorithm, name),
|
||||
.state = bun.new(T, T.init(.{})),
|
||||
.digest_length = digestLength(T),
|
||||
};
|
||||
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn update(self: *CryptoHasherZig, bytes: []const u8) void {
|
||||
inline for (algo_map) |item| {
|
||||
if (self.algorithm == @field(EVP.Algorithm, item[0])) {
|
||||
return item[1].update(@ptrCast(@alignCast(self.state)), bytes);
|
||||
}
|
||||
}
|
||||
@panic("unreachable");
|
||||
}
|
||||
|
||||
fn copy(self: *const CryptoHasherZig) CryptoHasherZig {
|
||||
inline for (algo_map) |item| {
|
||||
if (self.algorithm == @field(EVP.Algorithm, item[0])) {
|
||||
return .{
|
||||
.algorithm = self.algorithm,
|
||||
.state = bun.dupe(item[1], @ptrCast(@alignCast(self.state))),
|
||||
.digest_length = self.digest_length,
|
||||
};
|
||||
}
|
||||
}
|
||||
@panic("unreachable");
|
||||
}
|
||||
|
||||
fn finalWithLen(self: *CryptoHasherZig, output_digest_slice: []u8, res_len: usize) []u8 {
|
||||
inline for (algo_map) |pair| {
|
||||
const name, const T = pair;
|
||||
if (self.algorithm == @field(EVP.Algorithm, name)) {
|
||||
T.final(@ptrCast(@alignCast(self.state)), @ptrCast(output_digest_slice));
|
||||
const reset: *T = @ptrCast(@alignCast(self.state));
|
||||
reset.* = T.init(.{});
|
||||
return output_digest_slice[0..res_len];
|
||||
}
|
||||
}
|
||||
@panic("unreachable");
|
||||
}
|
||||
|
||||
fn final(self: *CryptoHasherZig, output_digest_slice: []u8) []u8 {
|
||||
return self.finalWithLen(output_digest_slice, self.digest_length);
|
||||
}
|
||||
|
||||
fn deinit(self: *CryptoHasherZig) void {
|
||||
inline for (algo_map) |item| {
|
||||
if (self.algorithm == @field(EVP.Algorithm, item[0])) {
|
||||
return bun.destroy(@as(*item[1], @ptrCast(@alignCast(self.state))));
|
||||
}
|
||||
}
|
||||
@panic("unreachable");
|
||||
}
|
||||
};
|
||||
|
||||
fn StaticCryptoHasher(comptime Hasher: type, comptime name: [:0]const u8) type {
|
||||
return struct {
|
||||
hashing: Hasher = Hasher{},
|
||||
digested: bool = false,
|
||||
|
||||
const ThisHasher = @This();
|
||||
|
||||
pub usingnamespace @field(JSC.Codegen, "JS" ++ name);
|
||||
|
||||
pub const digest = JSC.wrapInstanceMethod(ThisHasher, "digest_", false);
|
||||
pub const hash = JSC.wrapStaticMethod(ThisHasher, "hash_", false);
|
||||
|
||||
pub fn getByteLength(
|
||||
_: *@This(),
|
||||
_: *JSC.JSGlobalObject,
|
||||
) JSC.JSValue {
|
||||
return JSC.JSValue.jsNumber(@as(u16, Hasher.digest));
|
||||
}
|
||||
|
||||
pub fn getByteLengthStatic(
|
||||
_: *JSC.JSGlobalObject,
|
||||
_: JSValue,
|
||||
_: JSValue,
|
||||
) JSC.JSValue {
|
||||
return JSC.JSValue.jsNumber(@as(u16, Hasher.digest));
|
||||
}
|
||||
|
||||
fn hashToEncoding(globalThis: *JSGlobalObject, input: JSC.Node.BlobOrStringOrBuffer, encoding: JSC.Node.Encoding) bun.JSError!JSC.JSValue {
|
||||
var output_digest_buf: Hasher.Digest = undefined;
|
||||
|
||||
if (input == .blob and input.blob.isBunFile()) {
|
||||
return globalThis.throw("Bun.file() is not supported here yet (it needs an async version)", .{});
|
||||
}
|
||||
|
||||
if (comptime @typeInfo(@TypeOf(Hasher.hash)).@"fn".params.len == 3) {
|
||||
Hasher.hash(input.slice(), &output_digest_buf, JSC.VirtualMachine.get().rareData().boringEngine());
|
||||
} else {
|
||||
Hasher.hash(input.slice(), &output_digest_buf);
|
||||
}
|
||||
|
||||
return encoding.encodeWithSize(globalThis, Hasher.digest, &output_digest_buf);
|
||||
}
|
||||
|
||||
fn hashToBytes(globalThis: *JSGlobalObject, input: JSC.Node.BlobOrStringOrBuffer, output: ?JSC.ArrayBuffer) bun.JSError!JSC.JSValue {
|
||||
var output_digest_buf: Hasher.Digest = undefined;
|
||||
var output_digest_slice: *Hasher.Digest = &output_digest_buf;
|
||||
if (output) |output_buf| {
|
||||
var bytes = output_buf.byteSlice();
|
||||
if (bytes.len < Hasher.digest) {
|
||||
return globalThis.throwInvalidArguments(comptime std.fmt.comptimePrint("TypedArray must be at least {d} bytes", .{Hasher.digest}), .{});
|
||||
}
|
||||
output_digest_slice = bytes[0..Hasher.digest];
|
||||
}
|
||||
|
||||
if (comptime @typeInfo(@TypeOf(Hasher.hash)).@"fn".params.len == 3) {
|
||||
Hasher.hash(input.slice(), output_digest_slice, JSC.VirtualMachine.get().rareData().boringEngine());
|
||||
} else {
|
||||
Hasher.hash(input.slice(), output_digest_slice);
|
||||
}
|
||||
|
||||
if (output) |output_buf| {
|
||||
return output_buf.value;
|
||||
} else {
|
||||
var array_buffer_out = JSC.ArrayBuffer.fromBytes(bun.default_allocator.dupe(u8, output_digest_slice) catch unreachable, .Uint8Array);
|
||||
return array_buffer_out.toJSUnchecked(globalThis, null);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hash_(
|
||||
globalThis: *JSGlobalObject,
|
||||
input: JSC.Node.BlobOrStringOrBuffer,
|
||||
output: ?JSC.Node.StringOrBuffer,
|
||||
) bun.JSError!JSC.JSValue {
|
||||
defer input.deinit();
|
||||
|
||||
if (input == .blob and input.blob.isBunFile()) {
|
||||
return globalThis.throw("Bun.file() is not supported here yet (it needs an async version)", .{});
|
||||
}
|
||||
|
||||
if (output) |string_or_buffer| {
|
||||
switch (string_or_buffer) {
|
||||
inline else => |*str| {
|
||||
defer str.deinit();
|
||||
const encoding = JSC.Node.Encoding.from(str.slice()) orelse {
|
||||
return globalThis.ERR_INVALID_ARG_VALUE("Unknown encoding: {s}", .{str.slice()}).throw();
|
||||
};
|
||||
|
||||
return hashToEncoding(globalThis, input, encoding);
|
||||
},
|
||||
.buffer => |buffer| {
|
||||
return hashToBytes(globalThis, input, buffer.buffer);
|
||||
},
|
||||
}
|
||||
} else {
|
||||
return hashToBytes(globalThis, input, null);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn constructor(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!*@This() {
|
||||
const this = try bun.default_allocator.create(@This());
|
||||
this.* = .{ .hashing = Hasher.init() };
|
||||
return this;
|
||||
}
|
||||
|
||||
pub fn getter(
|
||||
globalObject: *JSC.JSGlobalObject,
|
||||
_: *JSC.JSObject,
|
||||
) JSC.JSValue {
|
||||
return ThisHasher.getConstructor(globalObject);
|
||||
}
|
||||
|
||||
pub fn update(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
||||
if (this.digested) {
|
||||
return globalThis.ERR_INVALID_STATE(name ++ " hasher already digested, create a new instance to update", .{}).throw();
|
||||
}
|
||||
const thisValue = callframe.this();
|
||||
const input = callframe.argument(0);
|
||||
const buffer = try JSC.Node.BlobOrStringOrBuffer.fromJS(globalThis, globalThis.bunVM().allocator, input) orelse {
|
||||
return globalThis.throwInvalidArguments("expected blob or string or buffer", .{});
|
||||
};
|
||||
defer buffer.deinit();
|
||||
|
||||
if (buffer == .blob and buffer.blob.isBunFile()) {
|
||||
return globalThis.throw("Bun.file() is not supported here yet (it needs an async version)", .{});
|
||||
}
|
||||
this.hashing.update(buffer.slice());
|
||||
return thisValue;
|
||||
}
|
||||
|
||||
pub fn digest_(
|
||||
this: *@This(),
|
||||
globalThis: *JSGlobalObject,
|
||||
output: ?JSC.Node.StringOrBuffer,
|
||||
) bun.JSError!JSC.JSValue {
|
||||
if (this.digested) {
|
||||
return globalThis.ERR_INVALID_STATE(name ++ " hasher already digested, create a new instance to digest again", .{}).throw();
|
||||
}
|
||||
if (output) |*string_or_buffer| {
|
||||
switch (string_or_buffer.*) {
|
||||
inline else => |*str| {
|
||||
defer str.deinit();
|
||||
const encoding = JSC.Node.Encoding.from(str.slice()) orelse {
|
||||
return globalThis.ERR_INVALID_ARG_VALUE("Unknown encoding: {s}", .{str.slice()}).throw();
|
||||
};
|
||||
|
||||
return this.digestToEncoding(globalThis, encoding);
|
||||
},
|
||||
.buffer => |*buffer| {
|
||||
return this.digestToBytes(
|
||||
globalThis,
|
||||
buffer.buffer,
|
||||
);
|
||||
},
|
||||
}
|
||||
} else {
|
||||
return this.digestToBytes(globalThis, null);
|
||||
}
|
||||
}
|
||||
|
||||
fn digestToBytes(this: *@This(), globalThis: *JSGlobalObject, output: ?JSC.ArrayBuffer) bun.JSError!JSC.JSValue {
|
||||
var output_digest_buf: Hasher.Digest = undefined;
|
||||
var output_digest_slice: *Hasher.Digest = &output_digest_buf;
|
||||
if (output) |output_buf| {
|
||||
var bytes = output_buf.byteSlice();
|
||||
if (bytes.len < Hasher.digest) {
|
||||
return globalThis.throwInvalidArguments(comptime std.fmt.comptimePrint("TypedArray must be at least {d} bytes", .{Hasher.digest}), .{});
|
||||
}
|
||||
output_digest_slice = bytes[0..Hasher.digest];
|
||||
} else {
|
||||
output_digest_buf = std.mem.zeroes(Hasher.Digest);
|
||||
}
|
||||
|
||||
this.hashing.final(output_digest_slice);
|
||||
this.digested = true;
|
||||
|
||||
if (output) |output_buf| {
|
||||
return output_buf.value;
|
||||
} else {
|
||||
var array_buffer_out = JSC.ArrayBuffer.fromBytes(bun.default_allocator.dupe(u8, &output_digest_buf) catch unreachable, .Uint8Array);
|
||||
return array_buffer_out.toJSUnchecked(globalThis, null);
|
||||
}
|
||||
}
|
||||
|
||||
fn digestToEncoding(this: *@This(), globalThis: *JSGlobalObject, encoding: JSC.Node.Encoding) JSC.JSValue {
|
||||
var output_digest_buf: Hasher.Digest = comptime brk: {
|
||||
var bytes: Hasher.Digest = undefined;
|
||||
var i: usize = 0;
|
||||
while (i < Hasher.digest) {
|
||||
bytes[i] = 0;
|
||||
i += 1;
|
||||
}
|
||||
break :brk bytes;
|
||||
};
|
||||
|
||||
const output_digest_slice: *Hasher.Digest = &output_digest_buf;
|
||||
|
||||
this.hashing.final(output_digest_slice);
|
||||
this.digested = true;
|
||||
|
||||
return encoding.encodeWithSize(globalThis, Hasher.digest, output_digest_slice);
|
||||
}
|
||||
|
||||
pub fn finalize(this: *@This()) void {
|
||||
VirtualMachine.get().allocator.destroy(this);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub const MD4 = StaticCryptoHasher(Hashers.MD4, "MD4");
|
||||
pub const MD5 = StaticCryptoHasher(Hashers.MD5, "MD5");
|
||||
pub const SHA1 = StaticCryptoHasher(Hashers.SHA1, "SHA1");
|
||||
pub const SHA224 = StaticCryptoHasher(Hashers.SHA224, "SHA224");
|
||||
pub const SHA256 = StaticCryptoHasher(Hashers.SHA256, "SHA256");
|
||||
pub const SHA384 = StaticCryptoHasher(Hashers.SHA384, "SHA384");
|
||||
pub const SHA512 = StaticCryptoHasher(Hashers.SHA512, "SHA512");
|
||||
pub const SHA512_256 = StaticCryptoHasher(Hashers.SHA512_256, "SHA512_256");
|
||||
const Crypto = JSC.API.Bun.Crypto;
|
||||
const Hashers = @import("../../../sha.zig");
|
||||
|
||||
const std = @import("std");
|
||||
const bun = @import("root").bun;
|
||||
const string = bun.string;
|
||||
const strings = bun.strings;
|
||||
const MutableString = bun.MutableString;
|
||||
const stringZ = bun.stringZ;
|
||||
const default_allocator = bun.default_allocator;
|
||||
const JSC = bun.JSC;
|
||||
const Async = bun.Async;
|
||||
const ZigString = JSC.ZigString;
|
||||
const JSValue = JSC.JSValue;
|
||||
const JSGlobalObject = JSC.JSGlobalObject;
|
||||
const CallFrame = JSC.CallFrame;
|
||||
const assert = bun.assert;
|
||||
const HMAC = Crypto.HMAC;
|
||||
const EVP = Crypto.EVP;
|
||||
const BoringSSL = bun.BoringSSL.c;
|
||||
const createCryptoError = Crypto.createCryptoError;
|
||||
const VirtualMachine = JSC.VirtualMachine;
|
||||
221
src/bun.js/api/crypto/EVP.zig
Normal file
221
src/bun.js/api/crypto/EVP.zig
Normal file
@@ -0,0 +1,221 @@
|
||||
ctx: BoringSSL.EVP_MD_CTX = undefined,
|
||||
md: *const BoringSSL.EVP_MD = undefined,
|
||||
algorithm: Algorithm,
|
||||
|
||||
// we do this to avoid asking BoringSSL what the digest name is
|
||||
// because that API is confusing
|
||||
pub const Algorithm = enum {
|
||||
// @"DSA-SHA",
|
||||
// @"DSA-SHA1",
|
||||
// @"MD5-SHA1",
|
||||
// @"RSA-MD5",
|
||||
// @"RSA-RIPEMD160",
|
||||
// @"RSA-SHA1",
|
||||
// @"RSA-SHA1-2",
|
||||
// @"RSA-SHA224",
|
||||
// @"RSA-SHA256",
|
||||
// @"RSA-SHA384",
|
||||
// @"RSA-SHA512",
|
||||
// @"ecdsa-with-SHA1",
|
||||
blake2b256,
|
||||
blake2b512,
|
||||
md4,
|
||||
md5,
|
||||
ripemd160,
|
||||
sha1,
|
||||
sha224,
|
||||
sha256,
|
||||
sha384,
|
||||
sha512,
|
||||
@"sha512-224",
|
||||
@"sha512-256",
|
||||
|
||||
@"sha3-224",
|
||||
@"sha3-256",
|
||||
@"sha3-384",
|
||||
@"sha3-512",
|
||||
shake128,
|
||||
shake256,
|
||||
|
||||
pub fn md(this: Algorithm) ?*const BoringSSL.EVP_MD {
|
||||
return switch (this) {
|
||||
.blake2b256 => BoringSSL.EVP_blake2b256(),
|
||||
.blake2b512 => BoringSSL.EVP_blake2b512(),
|
||||
.md4 => BoringSSL.EVP_md4(),
|
||||
.md5 => BoringSSL.EVP_md5(),
|
||||
.ripemd160 => BoringSSL.EVP_ripemd160(),
|
||||
.sha1 => BoringSSL.EVP_sha1(),
|
||||
.sha224 => BoringSSL.EVP_sha224(),
|
||||
.sha256 => BoringSSL.EVP_sha256(),
|
||||
.sha384 => BoringSSL.EVP_sha384(),
|
||||
.sha512 => BoringSSL.EVP_sha512(),
|
||||
.@"sha512-224" => BoringSSL.EVP_sha512_224(),
|
||||
.@"sha512-256" => BoringSSL.EVP_sha512_256(),
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
pub const names: std.EnumArray(Algorithm, bun.String) = brk: {
|
||||
var all = std.EnumArray(Algorithm, bun.String).initUndefined();
|
||||
var iter = all.iterator();
|
||||
while (iter.next()) |entry| {
|
||||
entry.value.* = bun.String.init(@tagName(entry.key));
|
||||
}
|
||||
break :brk all;
|
||||
};
|
||||
|
||||
pub const map = bun.ComptimeStringMap(Algorithm, .{
|
||||
.{ "blake2b256", .blake2b256 },
|
||||
.{ "blake2b512", .blake2b512 },
|
||||
.{ "ripemd160", .ripemd160 },
|
||||
.{ "rmd160", .ripemd160 },
|
||||
.{ "md4", .md4 },
|
||||
.{ "md5", .md5 },
|
||||
.{ "sha1", .sha1 },
|
||||
.{ "sha128", .sha1 },
|
||||
.{ "sha224", .sha224 },
|
||||
.{ "sha256", .sha256 },
|
||||
.{ "sha384", .sha384 },
|
||||
.{ "sha512", .sha512 },
|
||||
.{ "sha-1", .sha1 },
|
||||
.{ "sha-224", .sha224 },
|
||||
.{ "sha-256", .sha256 },
|
||||
.{ "sha-384", .sha384 },
|
||||
.{ "sha-512", .sha512 },
|
||||
.{ "sha-512/224", .@"sha512-224" },
|
||||
.{ "sha-512_224", .@"sha512-224" },
|
||||
.{ "sha-512224", .@"sha512-224" },
|
||||
.{ "sha512-224", .@"sha512-224" },
|
||||
.{ "sha-512/256", .@"sha512-256" },
|
||||
.{ "sha-512_256", .@"sha512-256" },
|
||||
.{ "sha-512256", .@"sha512-256" },
|
||||
.{ "sha512-256", .@"sha512-256" },
|
||||
.{ "sha384", .sha384 },
|
||||
.{ "sha3-224", .@"sha3-224" },
|
||||
.{ "sha3-256", .@"sha3-256" },
|
||||
.{ "sha3-384", .@"sha3-384" },
|
||||
.{ "sha3-512", .@"sha3-512" },
|
||||
.{ "shake128", .shake128 },
|
||||
.{ "shake256", .shake256 },
|
||||
// .{ "md5-sha1", .@"MD5-SHA1" },
|
||||
// .{ "dsa-sha", .@"DSA-SHA" },
|
||||
// .{ "dsa-sha1", .@"DSA-SHA1" },
|
||||
// .{ "ecdsa-with-sha1", .@"ecdsa-with-SHA1" },
|
||||
// .{ "rsa-md5", .@"RSA-MD5" },
|
||||
// .{ "rsa-sha1", .@"RSA-SHA1" },
|
||||
// .{ "rsa-sha1-2", .@"RSA-SHA1-2" },
|
||||
// .{ "rsa-sha224", .@"RSA-SHA224" },
|
||||
// .{ "rsa-sha256", .@"RSA-SHA256" },
|
||||
// .{ "rsa-sha384", .@"RSA-SHA384" },
|
||||
// .{ "rsa-sha512", .@"RSA-SHA512" },
|
||||
// .{ "rsa-ripemd160", .@"RSA-RIPEMD160" },
|
||||
});
|
||||
};
|
||||
|
||||
pub fn init(algorithm: Algorithm, md: *const BoringSSL.EVP_MD, engine: *BoringSSL.ENGINE) EVP {
|
||||
bun.BoringSSL.load();
|
||||
|
||||
var ctx: BoringSSL.EVP_MD_CTX = undefined;
|
||||
BoringSSL.EVP_MD_CTX_init(&ctx);
|
||||
_ = BoringSSL.EVP_DigestInit_ex(&ctx, md, engine);
|
||||
return .{
|
||||
.ctx = ctx,
|
||||
.md = md,
|
||||
.algorithm = algorithm,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn reset(this: *EVP, engine: *BoringSSL.ENGINE) void {
|
||||
BoringSSL.ERR_clear_error();
|
||||
_ = BoringSSL.EVP_DigestInit_ex(&this.ctx, this.md, engine);
|
||||
}
|
||||
|
||||
pub fn hash(this: *EVP, engine: *BoringSSL.ENGINE, input: []const u8, output: []u8) ?u32 {
|
||||
BoringSSL.ERR_clear_error();
|
||||
var outsize: c_uint = @min(@as(u16, @truncate(output.len)), this.size());
|
||||
if (BoringSSL.EVP_Digest(input.ptr, input.len, output.ptr, &outsize, this.md, engine) != 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return outsize;
|
||||
}
|
||||
|
||||
pub fn final(this: *EVP, engine: *BoringSSL.ENGINE, output: []u8) []u8 {
|
||||
BoringSSL.ERR_clear_error();
|
||||
var outsize: u32 = @min(@as(u16, @truncate(output.len)), this.size());
|
||||
if (BoringSSL.EVP_DigestFinal_ex(
|
||||
&this.ctx,
|
||||
output.ptr,
|
||||
&outsize,
|
||||
) != 1) {
|
||||
return "";
|
||||
}
|
||||
|
||||
this.reset(engine);
|
||||
|
||||
return output[0..outsize];
|
||||
}
|
||||
|
||||
pub fn update(this: *EVP, input: []const u8) void {
|
||||
BoringSSL.ERR_clear_error();
|
||||
_ = BoringSSL.EVP_DigestUpdate(&this.ctx, input.ptr, input.len);
|
||||
}
|
||||
|
||||
pub fn size(this: *const EVP) u16 {
|
||||
return @as(u16, @truncate(BoringSSL.EVP_MD_CTX_size(&this.ctx)));
|
||||
}
|
||||
|
||||
pub fn copy(this: *const EVP, engine: *BoringSSL.ENGINE) error{OutOfMemory}!EVP {
|
||||
BoringSSL.ERR_clear_error();
|
||||
var new = init(this.algorithm, this.md, engine);
|
||||
if (BoringSSL.EVP_MD_CTX_copy_ex(&new.ctx, &this.ctx) == 0) {
|
||||
return error.OutOfMemory;
|
||||
}
|
||||
return new;
|
||||
}
|
||||
|
||||
pub fn byNameAndEngine(engine: *BoringSSL.ENGINE, name: []const u8) ?EVP {
|
||||
if (Algorithm.map.getWithEql(name, strings.eqlCaseInsensitiveASCIIIgnoreLength)) |algorithm| {
|
||||
if (algorithm.md()) |md| {
|
||||
return EVP.init(algorithm, md, engine);
|
||||
}
|
||||
|
||||
if (BoringSSL.EVP_get_digestbyname(@tagName(algorithm))) |md| {
|
||||
return EVP.init(algorithm, md, engine);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn byName(name: ZigString, global: *JSC.JSGlobalObject) ?EVP {
|
||||
var name_str = name.toSlice(global.allocator());
|
||||
defer name_str.deinit();
|
||||
return byNameAndEngine(global.bunVM().rareData().boringEngine(), name_str.slice());
|
||||
}
|
||||
|
||||
pub fn deinit(this: *EVP) void {
|
||||
// https://github.com/oven-sh/bun/issues/3250
|
||||
_ = BoringSSL.EVP_MD_CTX_cleanup(&this.ctx);
|
||||
}
|
||||
|
||||
pub const Digest = [BoringSSL.EVP_MAX_MD_SIZE]u8;
|
||||
pub const PBKDF2 = @import("./PBKDF2.zig");
|
||||
pub const pbkdf2 = PBKDF2.pbkdf2;
|
||||
|
||||
const std = @import("std");
|
||||
const bun = @import("root").bun;
|
||||
const string = bun.string;
|
||||
const strings = bun.strings;
|
||||
const MutableString = bun.MutableString;
|
||||
const stringZ = bun.stringZ;
|
||||
const default_allocator = bun.default_allocator;
|
||||
const JSC = bun.JSC;
|
||||
const Async = bun.Async;
|
||||
const ZigString = JSC.ZigString;
|
||||
const JSValue = JSC.JSValue;
|
||||
const JSGlobalObject = JSC.JSGlobalObject;
|
||||
const CallFrame = JSC.CallFrame;
|
||||
const assert = bun.assert;
|
||||
const EVP = @This();
|
||||
const BoringSSL = bun.BoringSSL.c;
|
||||
56
src/bun.js/api/crypto/HMAC.zig
Normal file
56
src/bun.js/api/crypto/HMAC.zig
Normal file
@@ -0,0 +1,56 @@
|
||||
ctx: BoringSSL.HMAC_CTX,
|
||||
algorithm: EVP.Algorithm,
|
||||
|
||||
pub usingnamespace bun.New(@This());
|
||||
|
||||
pub fn init(algorithm: EVP.Algorithm, key: []const u8) ?*HMAC {
|
||||
const md = algorithm.md() orelse return null;
|
||||
var ctx: BoringSSL.HMAC_CTX = undefined;
|
||||
BoringSSL.HMAC_CTX_init(&ctx);
|
||||
if (BoringSSL.HMAC_Init_ex(&ctx, key.ptr, @intCast(key.len), md, null) != 1) {
|
||||
BoringSSL.HMAC_CTX_cleanup(&ctx);
|
||||
return null;
|
||||
}
|
||||
return HMAC.new(.{
|
||||
.ctx = ctx,
|
||||
.algorithm = algorithm,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn update(this: *HMAC, data: []const u8) void {
|
||||
_ = BoringSSL.HMAC_Update(&this.ctx, data.ptr, data.len);
|
||||
}
|
||||
|
||||
pub fn size(this: *const HMAC) usize {
|
||||
return BoringSSL.HMAC_size(&this.ctx);
|
||||
}
|
||||
|
||||
pub fn copy(this: *HMAC) !*HMAC {
|
||||
var ctx: BoringSSL.HMAC_CTX = undefined;
|
||||
BoringSSL.HMAC_CTX_init(&ctx);
|
||||
if (BoringSSL.HMAC_CTX_copy(&ctx, &this.ctx) != 1) {
|
||||
BoringSSL.HMAC_CTX_cleanup(&ctx);
|
||||
return error.BoringSSLError;
|
||||
}
|
||||
return HMAC.new(.{
|
||||
.ctx = ctx,
|
||||
.algorithm = this.algorithm,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn final(this: *HMAC, out: []u8) []u8 {
|
||||
var outlen: c_uint = undefined;
|
||||
_ = BoringSSL.HMAC_Final(&this.ctx, out.ptr, &outlen);
|
||||
return out[0..outlen];
|
||||
}
|
||||
|
||||
pub fn deinit(this: *HMAC) void {
|
||||
BoringSSL.HMAC_CTX_cleanup(&this.ctx);
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
const bun = @import("root").bun;
|
||||
const BoringSSL = bun.BoringSSL.c;
|
||||
const JSC = bun.JSC;
|
||||
const EVP = JSC.API.Bun.Crypto.EVP;
|
||||
const HMAC = @This();
|
||||
265
src/bun.js/api/crypto/PBKDF2.zig
Normal file
265
src/bun.js/api/crypto/PBKDF2.zig
Normal file
@@ -0,0 +1,265 @@
|
||||
password: JSC.Node.StringOrBuffer = JSC.Node.StringOrBuffer.empty,
|
||||
salt: JSC.Node.StringOrBuffer = JSC.Node.StringOrBuffer.empty,
|
||||
iteration_count: u32 = 1,
|
||||
length: i32 = 0,
|
||||
algorithm: EVP.Algorithm,
|
||||
|
||||
pub fn run(this: *PBKDF2, output: []u8) bool {
|
||||
const password = this.password.slice();
|
||||
const salt = this.salt.slice();
|
||||
const algorithm = this.algorithm;
|
||||
const iteration_count = this.iteration_count;
|
||||
const length = this.length;
|
||||
|
||||
@memset(output, 0);
|
||||
assert(this.length <= @as(i32, @intCast(output.len)));
|
||||
BoringSSL.ERR_clear_error();
|
||||
const rc = BoringSSL.PKCS5_PBKDF2_HMAC(
|
||||
if (password.len > 0) password.ptr else null,
|
||||
@intCast(password.len),
|
||||
salt.ptr,
|
||||
@intCast(salt.len),
|
||||
@intCast(iteration_count),
|
||||
algorithm.md().?,
|
||||
@intCast(length),
|
||||
output.ptr,
|
||||
);
|
||||
|
||||
if (rc <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
pub const Job = struct {
|
||||
pbkdf2: PBKDF2,
|
||||
output: []u8 = &[_]u8{},
|
||||
task: JSC.WorkPoolTask = .{ .callback = &runTask },
|
||||
promise: JSC.JSPromise.Strong = .{},
|
||||
vm: *JSC.VirtualMachine,
|
||||
err: ?u32 = null,
|
||||
any_task: JSC.AnyTask = undefined,
|
||||
poll: Async.KeepAlive = .{},
|
||||
|
||||
pub usingnamespace bun.New(@This());
|
||||
|
||||
pub fn runTask(task: *JSC.WorkPoolTask) void {
|
||||
const job: *PBKDF2.Job = @fieldParentPtr("task", task);
|
||||
defer job.vm.enqueueTaskConcurrent(JSC.ConcurrentTask.create(job.any_task.task()));
|
||||
job.output = bun.default_allocator.alloc(u8, @as(usize, @intCast(job.pbkdf2.length))) catch {
|
||||
job.err = BoringSSL.EVP_R_MEMORY_LIMIT_EXCEEDED;
|
||||
return;
|
||||
};
|
||||
if (!job.pbkdf2.run(job.output)) {
|
||||
job.err = BoringSSL.ERR_get_error();
|
||||
BoringSSL.ERR_clear_error();
|
||||
|
||||
bun.default_allocator.free(job.output);
|
||||
job.output = &[_]u8{};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn runFromJS(this: *Job) void {
|
||||
defer this.deinit();
|
||||
if (this.vm.isShuttingDown()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const globalThis = this.vm.global;
|
||||
const promise = this.promise.swap();
|
||||
if (this.err) |err| {
|
||||
promise.reject(globalThis, createCryptoError(globalThis, err));
|
||||
return;
|
||||
}
|
||||
|
||||
const output_slice = this.output;
|
||||
assert(output_slice.len == @as(usize, @intCast(this.pbkdf2.length)));
|
||||
const buffer_value = JSC.JSValue.createBuffer(globalThis, output_slice, bun.default_allocator);
|
||||
if (buffer_value == .zero) {
|
||||
promise.reject(globalThis, ZigString.init("Failed to create buffer").toErrorInstance(globalThis));
|
||||
return;
|
||||
}
|
||||
|
||||
this.output = &[_]u8{};
|
||||
promise.resolve(globalThis, buffer_value);
|
||||
}
|
||||
|
||||
pub fn deinit(this: *Job) void {
|
||||
this.poll.unref(this.vm);
|
||||
this.pbkdf2.deinitAndUnprotect();
|
||||
this.promise.deinit();
|
||||
bun.default_allocator.free(this.output);
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
pub fn create(vm: *JSC.VirtualMachine, globalThis: *JSC.JSGlobalObject, data: *const PBKDF2) *Job {
|
||||
var job = Job.new(.{
|
||||
.pbkdf2 = data.*,
|
||||
.vm = vm,
|
||||
.any_task = undefined,
|
||||
});
|
||||
|
||||
job.promise = JSC.JSPromise.Strong.init(globalThis);
|
||||
job.any_task = JSC.AnyTask.New(@This(), &runFromJS).init(job);
|
||||
job.poll.ref(vm);
|
||||
JSC.WorkPool.schedule(&job.task);
|
||||
|
||||
return job;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn deinitAndUnprotect(this: *PBKDF2) void {
|
||||
this.password.deinitAndUnprotect();
|
||||
this.salt.deinitAndUnprotect();
|
||||
}
|
||||
|
||||
pub fn deinit(this: *PBKDF2) void {
|
||||
this.password.deinit();
|
||||
this.salt.deinit();
|
||||
}
|
||||
|
||||
pub fn fromJS(globalThis: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame, is_async: bool) bun.JSError!PBKDF2 {
|
||||
const arg0, const arg1, const arg2, const arg3, const arg4, const arg5 = callFrame.argumentsAsArray(6);
|
||||
|
||||
if (!arg3.isNumber()) {
|
||||
return globalThis.throwInvalidArgumentTypeValue("keylen", "number", arg3);
|
||||
}
|
||||
|
||||
const keylen_num = arg3.asNumber();
|
||||
|
||||
if (std.math.isInf(keylen_num) or std.math.isNan(keylen_num)) {
|
||||
return globalThis.throwRangeError(keylen_num, .{
|
||||
.field_name = "keylen",
|
||||
.msg = "an integer",
|
||||
});
|
||||
}
|
||||
|
||||
if (keylen_num < 0 or keylen_num > std.math.maxInt(i32)) {
|
||||
return globalThis.throwRangeError(keylen_num, .{ .field_name = "keylen", .min = 0, .max = std.math.maxInt(i32) });
|
||||
}
|
||||
|
||||
const keylen: i32 = @intFromFloat(keylen_num);
|
||||
|
||||
if (globalThis.hasException()) {
|
||||
return error.JSError;
|
||||
}
|
||||
|
||||
if (!arg2.isAnyInt()) {
|
||||
return globalThis.throwInvalidArgumentTypeValue("iterations", "number", arg2);
|
||||
}
|
||||
|
||||
const iteration_count = arg2.coerce(i64, globalThis);
|
||||
|
||||
if (!globalThis.hasException() and (iteration_count < 1 or iteration_count > std.math.maxInt(i32))) {
|
||||
return globalThis.throwRangeError(iteration_count, .{ .field_name = "iterations", .min = 1, .max = std.math.maxInt(i32) + 1 });
|
||||
}
|
||||
|
||||
if (globalThis.hasException()) {
|
||||
return error.JSError;
|
||||
}
|
||||
|
||||
const algorithm = brk: {
|
||||
if (!arg4.isString()) {
|
||||
return globalThis.throwInvalidArgumentTypeValue("digest", "string", arg4);
|
||||
}
|
||||
|
||||
invalid: {
|
||||
switch (try EVP.Algorithm.map.fromJSCaseInsensitive(globalThis, arg4) orelse break :invalid) {
|
||||
.shake128, .shake256, .@"sha3-224", .@"sha3-256", .@"sha3-384", .@"sha3-512" => break :invalid,
|
||||
else => |alg| break :brk alg,
|
||||
}
|
||||
}
|
||||
|
||||
if (!globalThis.hasException()) {
|
||||
const slice = try arg4.toSlice(globalThis, bun.default_allocator);
|
||||
defer slice.deinit();
|
||||
const name = slice.slice();
|
||||
return globalThis.ERR_CRYPTO_INVALID_DIGEST("Invalid digest: {s}", .{name}).throw();
|
||||
}
|
||||
return error.JSError;
|
||||
};
|
||||
|
||||
var out = PBKDF2{
|
||||
.iteration_count = @intCast(iteration_count),
|
||||
.length = keylen,
|
||||
.algorithm = algorithm,
|
||||
};
|
||||
defer {
|
||||
if (globalThis.hasException()) {
|
||||
if (is_async)
|
||||
out.deinitAndUnprotect()
|
||||
else
|
||||
out.deinit();
|
||||
}
|
||||
}
|
||||
|
||||
const allow_string_object = true;
|
||||
out.salt = try JSC.Node.StringOrBuffer.fromJSMaybeAsync(globalThis, bun.default_allocator, arg1, is_async, allow_string_object) orelse {
|
||||
return globalThis.throwInvalidArgumentTypeValue("salt", "string or buffer", arg1);
|
||||
};
|
||||
|
||||
if (out.salt.slice().len > std.math.maxInt(i32)) {
|
||||
return globalThis.throwInvalidArguments("salt is too long", .{});
|
||||
}
|
||||
|
||||
out.password = try JSC.Node.StringOrBuffer.fromJSMaybeAsync(globalThis, bun.default_allocator, arg0, is_async, allow_string_object) orelse {
|
||||
return globalThis.throwInvalidArgumentTypeValue("password", "string or buffer", arg0);
|
||||
};
|
||||
|
||||
if (out.password.slice().len > std.math.maxInt(i32)) {
|
||||
return globalThis.throwInvalidArguments("password is too long", .{});
|
||||
}
|
||||
|
||||
if (is_async) {
|
||||
if (!arg5.isFunction()) {
|
||||
return globalThis.throwInvalidArgumentTypeValue("callback", "function", arg5);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/// For usage in Zig
|
||||
pub fn pbkdf2(
|
||||
output: []u8,
|
||||
password: []const u8,
|
||||
salt: []const u8,
|
||||
iteration_count: u32,
|
||||
algorithm: Algorithm,
|
||||
) ?[]const u8 {
|
||||
var pbk = PBKDF2{
|
||||
.algorithm = algorithm,
|
||||
.password = JSC.Node.StringOrBuffer{ .encoded_slice = JSC.ZigString.Slice.fromUTF8NeverFree(password) },
|
||||
.salt = JSC.Node.StringOrBuffer{ .encoded_slice = JSC.ZigString.Slice.fromUTF8NeverFree(salt) },
|
||||
.iteration_count = iteration_count,
|
||||
.length = @intCast(output.len),
|
||||
};
|
||||
|
||||
if (!pbk.run(output)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
const std = @import("std");
|
||||
const bun = @import("root").bun;
|
||||
const string = bun.string;
|
||||
const strings = bun.strings;
|
||||
const MutableString = bun.MutableString;
|
||||
const stringZ = bun.stringZ;
|
||||
const default_allocator = bun.default_allocator;
|
||||
const JSC = bun.JSC;
|
||||
const Async = bun.Async;
|
||||
const ZigString = JSC.ZigString;
|
||||
const JSValue = JSC.JSValue;
|
||||
const JSGlobalObject = JSC.JSGlobalObject;
|
||||
const CallFrame = JSC.CallFrame;
|
||||
const assert = bun.assert;
|
||||
const EVP = JSC.API.Bun.Crypto.EVP;
|
||||
const Algorithm = EVP.Algorithm;
|
||||
const BoringSSL = bun.BoringSSL.c;
|
||||
const createCryptoError = JSC.API.Bun.Crypto.createCryptoError;
|
||||
const VirtualMachine = JSC.VirtualMachine;
|
||||
const PBKDF2 = @This();
|
||||
772
src/bun.js/api/crypto/PasswordObject.zig
Normal file
772
src/bun.js/api/crypto/PasswordObject.zig
Normal file
@@ -0,0 +1,772 @@
|
||||
pub const PasswordObject = struct {
|
||||
pub const pwhash = std.crypto.pwhash;
|
||||
pub const Algorithm = enum {
|
||||
argon2i,
|
||||
argon2d,
|
||||
argon2id,
|
||||
bcrypt,
|
||||
|
||||
pub const Value = union(Algorithm) {
|
||||
argon2i: Argon2Params,
|
||||
argon2d: Argon2Params,
|
||||
argon2id: Argon2Params,
|
||||
// bcrypt only accepts "cost"
|
||||
bcrypt: u6,
|
||||
|
||||
pub const bcrpyt_default = 10;
|
||||
|
||||
pub const default = Algorithm.Value{
|
||||
.argon2id = .{},
|
||||
};
|
||||
|
||||
pub fn fromJS(globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) bun.JSError!Value {
|
||||
if (value.isObject()) {
|
||||
if (try value.getTruthy(globalObject, "algorithm")) |algorithm_value| {
|
||||
if (!algorithm_value.isString()) {
|
||||
return globalObject.throwInvalidArgumentType("hash", "algorithm", "string");
|
||||
}
|
||||
|
||||
const algorithm_string = try algorithm_value.getZigString(globalObject);
|
||||
|
||||
switch (PasswordObject.Algorithm.label.getWithEql(algorithm_string, JSC.ZigString.eqlComptime) orelse {
|
||||
return globalObject.throwInvalidArgumentType("hash", "algorithm", unknown_password_algorithm_message);
|
||||
}) {
|
||||
.bcrypt => {
|
||||
var algorithm = PasswordObject.Algorithm.Value{
|
||||
.bcrypt = PasswordObject.Algorithm.Value.bcrpyt_default,
|
||||
};
|
||||
|
||||
if (try value.getTruthy(globalObject, "cost")) |rounds_value| {
|
||||
if (!rounds_value.isNumber()) {
|
||||
return globalObject.throwInvalidArgumentType("hash", "cost", "number");
|
||||
}
|
||||
|
||||
const rounds = rounds_value.coerce(i32, globalObject);
|
||||
|
||||
if (rounds < 4 or rounds > 31) {
|
||||
return globalObject.throwInvalidArguments("Rounds must be between 4 and 31", .{});
|
||||
}
|
||||
|
||||
algorithm.bcrypt = @as(u6, @intCast(rounds));
|
||||
}
|
||||
|
||||
return algorithm;
|
||||
},
|
||||
inline .argon2id, .argon2d, .argon2i => |tag| {
|
||||
var argon = Algorithm.Argon2Params{};
|
||||
|
||||
if (try value.getTruthy(globalObject, "timeCost")) |time_value| {
|
||||
if (!time_value.isNumber()) {
|
||||
return globalObject.throwInvalidArgumentType("hash", "timeCost", "number");
|
||||
}
|
||||
|
||||
const time_cost = time_value.coerce(i32, globalObject);
|
||||
|
||||
if (time_cost < 1) {
|
||||
return globalObject.throwInvalidArguments("Time cost must be greater than 0", .{});
|
||||
}
|
||||
|
||||
argon.time_cost = @as(u32, @intCast(time_cost));
|
||||
}
|
||||
|
||||
if (try value.getTruthy(globalObject, "memoryCost")) |memory_value| {
|
||||
if (!memory_value.isNumber()) {
|
||||
return globalObject.throwInvalidArgumentType("hash", "memoryCost", "number");
|
||||
}
|
||||
|
||||
const memory_cost = memory_value.coerce(i32, globalObject);
|
||||
|
||||
if (memory_cost < 1) {
|
||||
return globalObject.throwInvalidArguments("Memory cost must be greater than 0", .{});
|
||||
}
|
||||
|
||||
argon.memory_cost = @as(u32, @intCast(memory_cost));
|
||||
}
|
||||
|
||||
return @unionInit(Algorithm.Value, @tagName(tag), argon);
|
||||
},
|
||||
}
|
||||
|
||||
unreachable;
|
||||
} else {
|
||||
return globalObject.throwInvalidArgumentType("hash", "options.algorithm", "string");
|
||||
}
|
||||
} else if (value.isString()) {
|
||||
const algorithm_string = try value.getZigString(globalObject);
|
||||
|
||||
switch (PasswordObject.Algorithm.label.getWithEql(algorithm_string, JSC.ZigString.eqlComptime) orelse {
|
||||
return globalObject.throwInvalidArgumentType("hash", "algorithm", unknown_password_algorithm_message);
|
||||
}) {
|
||||
.bcrypt => {
|
||||
return PasswordObject.Algorithm.Value{
|
||||
.bcrypt = PasswordObject.Algorithm.Value.bcrpyt_default,
|
||||
};
|
||||
},
|
||||
.argon2id => {
|
||||
return PasswordObject.Algorithm.Value{
|
||||
.argon2id = .{},
|
||||
};
|
||||
},
|
||||
.argon2d => {
|
||||
return PasswordObject.Algorithm.Value{
|
||||
.argon2d = .{},
|
||||
};
|
||||
},
|
||||
.argon2i => {
|
||||
return PasswordObject.Algorithm.Value{
|
||||
.argon2i = .{},
|
||||
};
|
||||
},
|
||||
}
|
||||
} else {
|
||||
return globalObject.throwInvalidArgumentType("hash", "algorithm", "string");
|
||||
}
|
||||
|
||||
unreachable;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Argon2Params = struct {
|
||||
// we don't support the other options right now, but can add them later if someone asks
|
||||
memory_cost: u32 = pwhash.argon2.Params.interactive_2id.m,
|
||||
time_cost: u32 = pwhash.argon2.Params.interactive_2id.t,
|
||||
|
||||
pub fn toParams(this: Argon2Params) pwhash.argon2.Params {
|
||||
return pwhash.argon2.Params{
|
||||
.t = this.time_cost,
|
||||
.m = this.memory_cost,
|
||||
.p = 1,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const argon2 = Algorithm.argon2id;
|
||||
|
||||
pub const label = bun.ComptimeStringMap(
|
||||
Algorithm,
|
||||
.{
|
||||
.{ "argon2i", .argon2i },
|
||||
.{ "argon2d", .argon2d },
|
||||
.{ "argon2id", .argon2id },
|
||||
.{ "bcrypt", .bcrypt },
|
||||
},
|
||||
);
|
||||
|
||||
pub const default = Algorithm.argon2;
|
||||
|
||||
pub fn get(pw: []const u8) ?Algorithm {
|
||||
if (pw[0] != '$') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// PHC format looks like $<algorithm>$<params>$<salt>$<hash><optional stuff>
|
||||
if (strings.hasPrefixComptime(pw[1..], "argon2d$")) {
|
||||
return .argon2d;
|
||||
}
|
||||
if (strings.hasPrefixComptime(pw[1..], "argon2i$")) {
|
||||
return .argon2i;
|
||||
}
|
||||
if (strings.hasPrefixComptime(pw[1..], "argon2id$")) {
|
||||
return .argon2id;
|
||||
}
|
||||
|
||||
if (strings.hasPrefixComptime(pw[1..], "bcrypt")) {
|
||||
return .bcrypt;
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/Crypt_(C)
|
||||
if (strings.hasPrefixComptime(pw[1..], "2")) {
|
||||
return .bcrypt;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
pub const HashError = pwhash.Error || error{UnsupportedAlgorithm};
|
||||
|
||||
// This is purposely simple because nobody asked to make it more complicated
|
||||
pub fn hash(
|
||||
allocator: std.mem.Allocator,
|
||||
password: []const u8,
|
||||
algorithm: Algorithm.Value,
|
||||
) HashError![]const u8 {
|
||||
switch (algorithm) {
|
||||
inline .argon2i, .argon2d, .argon2id => |argon| {
|
||||
var outbuf: [4096]u8 = undefined;
|
||||
const hash_options = pwhash.argon2.HashOptions{
|
||||
.params = argon.toParams(),
|
||||
.allocator = allocator,
|
||||
.mode = switch (algorithm) {
|
||||
.argon2i => .argon2i,
|
||||
.argon2d => .argon2d,
|
||||
.argon2id => .argon2id,
|
||||
else => unreachable,
|
||||
},
|
||||
.encoding = .phc,
|
||||
};
|
||||
// warning: argon2's code may spin up threads if paralellism is set to > 0
|
||||
// we don't expose this option
|
||||
// but since it parses from phc format, it's possible that it will be set
|
||||
// eventually we should do something that about that.
|
||||
const out_bytes = try pwhash.argon2.strHash(password, hash_options, &outbuf);
|
||||
return try allocator.dupe(u8, out_bytes);
|
||||
},
|
||||
.bcrypt => |cost| {
|
||||
var outbuf: [4096]u8 = undefined;
|
||||
var outbuf_slice: []u8 = outbuf[0..];
|
||||
var password_to_use = password;
|
||||
// bcrypt silently truncates passwords longer than 72 bytes
|
||||
// we use SHA512 to hash the password if it's longer than 72 bytes
|
||||
if (password.len > 72) {
|
||||
var sha_512 = bun.sha.SHA512.init();
|
||||
defer sha_512.deinit();
|
||||
sha_512.update(password);
|
||||
sha_512.final(outbuf[0..bun.sha.SHA512.digest]);
|
||||
password_to_use = outbuf[0..bun.sha.SHA512.digest];
|
||||
outbuf_slice = outbuf[bun.sha.SHA512.digest..];
|
||||
}
|
||||
|
||||
const hash_options = pwhash.bcrypt.HashOptions{
|
||||
.params = pwhash.bcrypt.Params{
|
||||
.rounds_log = cost,
|
||||
.silently_truncate_password = true,
|
||||
},
|
||||
.allocator = allocator,
|
||||
.encoding = .crypt,
|
||||
};
|
||||
const out_bytes = try pwhash.bcrypt.strHash(password_to_use, hash_options, outbuf_slice);
|
||||
return try allocator.dupe(u8, out_bytes);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify(
|
||||
allocator: std.mem.Allocator,
|
||||
password: []const u8,
|
||||
previous_hash: []const u8,
|
||||
algorithm: ?Algorithm,
|
||||
) HashError!bool {
|
||||
if (previous_hash.len == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return verifyWithAlgorithm(
|
||||
allocator,
|
||||
password,
|
||||
previous_hash,
|
||||
algorithm orelse Algorithm.get(previous_hash) orelse return error.UnsupportedAlgorithm,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn verifyWithAlgorithm(
|
||||
allocator: std.mem.Allocator,
|
||||
password: []const u8,
|
||||
previous_hash: []const u8,
|
||||
algorithm: Algorithm,
|
||||
) HashError!bool {
|
||||
switch (algorithm) {
|
||||
.argon2id, .argon2d, .argon2i => {
|
||||
pwhash.argon2.strVerify(previous_hash, password, .{ .allocator = allocator }) catch |err| {
|
||||
if (err == error.PasswordVerificationFailed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return err;
|
||||
};
|
||||
return true;
|
||||
},
|
||||
.bcrypt => {
|
||||
var password_to_use = password;
|
||||
var outbuf: [bun.sha.SHA512.digest]u8 = undefined;
|
||||
|
||||
// bcrypt silently truncates passwords longer than 72 bytes
|
||||
// we use SHA512 to hash the password if it's longer than 72 bytes
|
||||
if (password.len > 72) {
|
||||
var sha_512 = bun.sha.SHA512.init();
|
||||
defer sha_512.deinit();
|
||||
sha_512.update(password);
|
||||
sha_512.final(&outbuf);
|
||||
password_to_use = &outbuf;
|
||||
}
|
||||
pwhash.bcrypt.strVerify(previous_hash, password_to_use, .{
|
||||
.allocator = allocator,
|
||||
.silently_truncate_password = true,
|
||||
}) catch |err| {
|
||||
if (err == error.PasswordVerificationFailed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return err;
|
||||
};
|
||||
return true;
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const JSPasswordObject = struct {
|
||||
const PascalToUpperUnderscoreCaseFormatter = struct {
|
||||
input: []const u8,
|
||||
pub fn format(self: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
||||
for (self.input) |c| {
|
||||
if (std.ascii.isUpper(c)) {
|
||||
try writer.writeByte('_');
|
||||
try writer.writeByte(c);
|
||||
} else if (std.ascii.isLower(c)) {
|
||||
try writer.writeByte(std.ascii.toUpper(c));
|
||||
} else {
|
||||
try writer.writeByte(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub export fn JSPasswordObject__create(globalObject: *JSC.JSGlobalObject) JSC.JSValue {
|
||||
var object = JSValue.createEmptyObject(globalObject, 4);
|
||||
object.put(
|
||||
globalObject,
|
||||
ZigString.static("hash"),
|
||||
JSC.createCallback(globalObject, ZigString.static("hash"), 2, JSPasswordObject__hash),
|
||||
);
|
||||
object.put(
|
||||
globalObject,
|
||||
ZigString.static("hashSync"),
|
||||
JSC.createCallback(globalObject, ZigString.static("hashSync"), 2, JSPasswordObject__hashSync),
|
||||
);
|
||||
object.put(
|
||||
globalObject,
|
||||
ZigString.static("verify"),
|
||||
JSC.createCallback(globalObject, ZigString.static("verify"), 2, JSPasswordObject__verify),
|
||||
);
|
||||
object.put(
|
||||
globalObject,
|
||||
ZigString.static("verifySync"),
|
||||
JSC.createCallback(globalObject, ZigString.static("verifySync"), 2, JSPasswordObject__verifySync),
|
||||
);
|
||||
return object;
|
||||
}
|
||||
|
||||
const HashJob = struct {
|
||||
algorithm: PasswordObject.Algorithm.Value,
|
||||
password: []const u8,
|
||||
promise: JSC.JSPromise.Strong,
|
||||
event_loop: *JSC.EventLoop,
|
||||
global: *JSC.JSGlobalObject,
|
||||
ref: Async.KeepAlive = .{},
|
||||
task: JSC.WorkPoolTask = .{ .callback = &run },
|
||||
|
||||
pub usingnamespace bun.New(@This());
|
||||
|
||||
pub const Result = struct {
|
||||
value: Value,
|
||||
ref: Async.KeepAlive = .{},
|
||||
|
||||
task: JSC.AnyTask = undefined,
|
||||
promise: JSC.JSPromise.Strong,
|
||||
global: *JSC.JSGlobalObject,
|
||||
|
||||
pub usingnamespace bun.New(@This());
|
||||
|
||||
pub const Value = union(enum) {
|
||||
err: PasswordObject.HashError,
|
||||
hash: []const u8,
|
||||
|
||||
pub fn toErrorInstance(this: Value, globalObject: *JSC.JSGlobalObject) JSC.JSValue {
|
||||
const error_code = std.fmt.allocPrint(bun.default_allocator, "PASSWORD{}", .{PascalToUpperUnderscoreCaseFormatter{ .input = @errorName(this.err) }}) catch bun.outOfMemory();
|
||||
defer bun.default_allocator.free(error_code);
|
||||
const instance = globalObject.createErrorInstance("Password hashing failed with error \"{s}\"", .{@errorName(this.err)});
|
||||
instance.put(globalObject, ZigString.static("code"), JSC.ZigString.init(error_code).toJS(globalObject));
|
||||
return instance;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn runFromJS(this: *Result) void {
|
||||
var promise = this.promise;
|
||||
defer promise.deinit();
|
||||
this.promise = .{};
|
||||
this.ref.unref(this.global.bunVM());
|
||||
const global = this.global;
|
||||
switch (this.value) {
|
||||
.err => {
|
||||
const error_instance = this.value.toErrorInstance(global);
|
||||
this.destroy();
|
||||
promise.reject(global, error_instance);
|
||||
},
|
||||
.hash => |value| {
|
||||
const js_string = JSC.ZigString.init(value).toJS(global);
|
||||
this.destroy();
|
||||
promise.resolve(global, js_string);
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub fn deinit(this: *HashJob) void {
|
||||
this.promise.deinit();
|
||||
bun.freeSensitive(bun.default_allocator, this.password);
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
pub fn getValue(password: []const u8, algorithm: PasswordObject.Algorithm.Value) Result.Value {
|
||||
const value = PasswordObject.hash(bun.default_allocator, password, algorithm) catch |err| {
|
||||
return Result.Value{ .err = err };
|
||||
};
|
||||
return Result.Value{ .hash = value };
|
||||
}
|
||||
|
||||
pub fn run(task: *bun.ThreadPool.Task) void {
|
||||
var this: *HashJob = @fieldParentPtr("task", task);
|
||||
|
||||
var result = Result.new(.{
|
||||
.value = getValue(this.password, this.algorithm),
|
||||
.task = undefined,
|
||||
.promise = this.promise,
|
||||
.global = this.global,
|
||||
.ref = this.ref,
|
||||
});
|
||||
this.promise = .empty;
|
||||
|
||||
result.task = JSC.AnyTask.New(Result, Result.runFromJS).init(result);
|
||||
this.ref = .{};
|
||||
this.event_loop.enqueueTaskConcurrent(JSC.ConcurrentTask.createFrom(&result.task));
|
||||
this.deinit();
|
||||
}
|
||||
};
|
||||
|
||||
pub fn hash(globalObject: *JSC.JSGlobalObject, password: []const u8, algorithm: PasswordObject.Algorithm.Value, comptime sync: bool) bun.JSError!JSC.JSValue {
|
||||
assert(password.len > 0); // caller must check
|
||||
|
||||
if (comptime sync) {
|
||||
const value = HashJob.getValue(password, algorithm);
|
||||
switch (value) {
|
||||
.err => {
|
||||
const error_instance = value.toErrorInstance(globalObject);
|
||||
return globalObject.throwValue(error_instance);
|
||||
},
|
||||
.hash => |h| {
|
||||
return JSC.ZigString.init(h).toJS(globalObject);
|
||||
},
|
||||
}
|
||||
|
||||
unreachable;
|
||||
}
|
||||
|
||||
const promise = JSC.JSPromise.Strong.init(globalObject);
|
||||
|
||||
var job = HashJob.new(.{
|
||||
.algorithm = algorithm,
|
||||
.password = password,
|
||||
.promise = promise,
|
||||
.event_loop = globalObject.bunVM().eventLoop(),
|
||||
.global = globalObject,
|
||||
});
|
||||
job.ref.ref(globalObject.bunVM());
|
||||
JSC.WorkPool.schedule(&job.task);
|
||||
|
||||
return promise.value();
|
||||
}
|
||||
|
||||
pub fn verify(globalObject: *JSC.JSGlobalObject, password: []const u8, prev_hash: []const u8, algorithm: ?PasswordObject.Algorithm, comptime sync: bool) bun.JSError!JSC.JSValue {
|
||||
assert(password.len > 0); // caller must check
|
||||
|
||||
if (comptime sync) {
|
||||
const value = VerifyJob.getValue(password, prev_hash, algorithm);
|
||||
switch (value) {
|
||||
.err => {
|
||||
const error_instance = value.toErrorInstance(globalObject);
|
||||
return globalObject.throwValue(error_instance);
|
||||
},
|
||||
.pass => |pass| {
|
||||
return JSC.JSValue.jsBoolean(pass);
|
||||
},
|
||||
}
|
||||
|
||||
unreachable;
|
||||
}
|
||||
|
||||
var promise = JSC.JSPromise.Strong.init(globalObject);
|
||||
|
||||
const job = VerifyJob.new(.{
|
||||
.algorithm = algorithm,
|
||||
.password = password,
|
||||
.prev_hash = prev_hash,
|
||||
.promise = promise,
|
||||
.event_loop = globalObject.bunVM().eventLoop(),
|
||||
.global = globalObject,
|
||||
});
|
||||
job.ref.ref(globalObject.bunVM());
|
||||
JSC.WorkPool.schedule(&job.task);
|
||||
|
||||
return promise.value();
|
||||
}
|
||||
|
||||
// Once we have bindings generator, this should be replaced with a generated function
|
||||
pub fn JSPasswordObject__hash(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
||||
const arguments_ = callframe.arguments_old(2);
|
||||
const arguments = arguments_.ptr[0..arguments_.len];
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalObject.throwNotEnoughArguments("hash", 1, 0);
|
||||
}
|
||||
|
||||
var algorithm = PasswordObject.Algorithm.Value.default;
|
||||
|
||||
if (arguments.len > 1 and !arguments[1].isEmptyOrUndefinedOrNull()) {
|
||||
algorithm = try PasswordObject.Algorithm.Value.fromJS(globalObject, arguments[1]);
|
||||
}
|
||||
|
||||
// TODO: this most likely should error like `hashSync` instead of stringifying.
|
||||
//
|
||||
// fromJS(...) orelse {
|
||||
// return globalObject.throwInvalidArgumentType("hash", "password", "string or TypedArray");
|
||||
// }
|
||||
const password_to_hash = try JSC.Node.StringOrBuffer.fromJSToOwnedSlice(globalObject, arguments[0], bun.default_allocator);
|
||||
errdefer bun.default_allocator.free(password_to_hash);
|
||||
|
||||
if (password_to_hash.len == 0) {
|
||||
return globalObject.throwInvalidArguments("password must not be empty", .{});
|
||||
}
|
||||
|
||||
return hash(globalObject, password_to_hash, algorithm, false);
|
||||
}
|
||||
|
||||
// Once we have bindings generator, this should be replaced with a generated function
|
||||
pub fn JSPasswordObject__hashSync(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
||||
const arguments_ = callframe.arguments_old(2);
|
||||
const arguments = arguments_.ptr[0..arguments_.len];
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalObject.throwNotEnoughArguments("hash", 1, 0);
|
||||
}
|
||||
|
||||
var algorithm = PasswordObject.Algorithm.Value.default;
|
||||
|
||||
if (arguments.len > 1 and !arguments[1].isEmptyOrUndefinedOrNull()) {
|
||||
algorithm = try PasswordObject.Algorithm.Value.fromJS(globalObject, arguments[1]);
|
||||
}
|
||||
|
||||
var string_or_buffer = try JSC.Node.StringOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[0]) orelse {
|
||||
return globalObject.throwInvalidArgumentType("hash", "password", "string or TypedArray");
|
||||
};
|
||||
defer string_or_buffer.deinit();
|
||||
|
||||
if (string_or_buffer.slice().len == 0) {
|
||||
return globalObject.throwInvalidArguments("password must not be empty", .{});
|
||||
}
|
||||
|
||||
return hash(globalObject, string_or_buffer.slice(), algorithm, true);
|
||||
}
|
||||
|
||||
const VerifyJob = struct {
|
||||
algorithm: ?PasswordObject.Algorithm = null,
|
||||
password: []const u8,
|
||||
prev_hash: []const u8,
|
||||
promise: JSC.JSPromise.Strong,
|
||||
event_loop: *JSC.EventLoop,
|
||||
global: *JSC.JSGlobalObject,
|
||||
ref: Async.KeepAlive = .{},
|
||||
task: JSC.WorkPoolTask = .{ .callback = &run },
|
||||
|
||||
pub usingnamespace bun.New(@This());
|
||||
|
||||
pub const Result = struct {
|
||||
value: Value,
|
||||
ref: Async.KeepAlive = .{},
|
||||
|
||||
task: JSC.AnyTask = undefined,
|
||||
promise: JSC.JSPromise.Strong,
|
||||
global: *JSC.JSGlobalObject,
|
||||
|
||||
pub usingnamespace bun.New(@This());
|
||||
|
||||
pub const Value = union(enum) {
|
||||
err: PasswordObject.HashError,
|
||||
pass: bool,
|
||||
|
||||
pub fn toErrorInstance(this: Value, globalObject: *JSC.JSGlobalObject) JSC.JSValue {
|
||||
const error_code = std.fmt.allocPrint(bun.default_allocator, "PASSWORD{}", .{PascalToUpperUnderscoreCaseFormatter{ .input = @errorName(this.err) }}) catch bun.outOfMemory();
|
||||
defer bun.default_allocator.free(error_code);
|
||||
const instance = globalObject.createErrorInstance("Password verification failed with error \"{s}\"", .{@errorName(this.err)});
|
||||
instance.put(globalObject, ZigString.static("code"), JSC.ZigString.init(error_code).toJS(globalObject));
|
||||
return instance;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn runFromJS(this: *Result) void {
|
||||
var promise = this.promise;
|
||||
defer promise.deinit();
|
||||
this.promise = .{};
|
||||
this.ref.unref(this.global.bunVM());
|
||||
const global = this.global;
|
||||
switch (this.value) {
|
||||
.err => {
|
||||
const error_instance = this.value.toErrorInstance(global);
|
||||
this.destroy();
|
||||
promise.reject(global, error_instance);
|
||||
},
|
||||
.pass => |pass| {
|
||||
this.destroy();
|
||||
promise.resolve(global, JSC.JSValue.jsBoolean(pass));
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub fn deinit(this: *VerifyJob) void {
|
||||
this.promise.deinit();
|
||||
|
||||
bun.freeSensitive(bun.default_allocator, this.password);
|
||||
bun.freeSensitive(bun.default_allocator, this.prev_hash);
|
||||
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
pub fn getValue(password: []const u8, prev_hash: []const u8, algorithm: ?PasswordObject.Algorithm) Result.Value {
|
||||
const pass = PasswordObject.verify(bun.default_allocator, password, prev_hash, algorithm) catch |err| {
|
||||
return Result.Value{ .err = err };
|
||||
};
|
||||
return Result.Value{ .pass = pass };
|
||||
}
|
||||
|
||||
pub fn run(task: *bun.ThreadPool.Task) void {
|
||||
var this: *VerifyJob = @fieldParentPtr("task", task);
|
||||
|
||||
var result = Result.new(.{
|
||||
.value = getValue(this.password, this.prev_hash, this.algorithm),
|
||||
.task = undefined,
|
||||
.promise = this.promise,
|
||||
.global = this.global,
|
||||
.ref = this.ref,
|
||||
});
|
||||
this.promise = .empty;
|
||||
|
||||
result.task = JSC.AnyTask.New(Result, Result.runFromJS).init(result);
|
||||
this.ref = .{};
|
||||
this.event_loop.enqueueTaskConcurrent(JSC.ConcurrentTask.createFrom(&result.task));
|
||||
this.deinit();
|
||||
}
|
||||
};
|
||||
|
||||
// Once we have bindings generator, this should be replaced with a generated function
|
||||
pub fn JSPasswordObject__verify(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
||||
const arguments_ = callframe.arguments_old(3);
|
||||
const arguments = arguments_.ptr[0..arguments_.len];
|
||||
|
||||
if (arguments.len < 2) {
|
||||
return globalObject.throwNotEnoughArguments("verify", 2, 0);
|
||||
}
|
||||
|
||||
var algorithm: ?PasswordObject.Algorithm = null;
|
||||
|
||||
if (arguments.len > 2 and !arguments[2].isEmptyOrUndefinedOrNull()) {
|
||||
if (!arguments[2].isString()) {
|
||||
return globalObject.throwInvalidArgumentType("verify", "algorithm", "string");
|
||||
}
|
||||
|
||||
const algorithm_string = try arguments[2].getZigString(globalObject);
|
||||
|
||||
algorithm = PasswordObject.Algorithm.label.getWithEql(algorithm_string, JSC.ZigString.eqlComptime) orelse {
|
||||
if (!globalObject.hasException()) {
|
||||
return globalObject.throwInvalidArgumentType("verify", "algorithm", unknown_password_algorithm_message);
|
||||
}
|
||||
return error.JSError;
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: this most likely should error like `verifySync` instead of stringifying.
|
||||
//
|
||||
// fromJS(...) orelse {
|
||||
// return globalObject.throwInvalidArgumentType("hash", "password", "string or TypedArray");
|
||||
// }
|
||||
const owned_password = try JSC.Node.StringOrBuffer.fromJSToOwnedSlice(globalObject, arguments[0], bun.default_allocator);
|
||||
|
||||
// TODO: this most likely should error like `verifySync` instead of stringifying.
|
||||
//
|
||||
// fromJS(...) orelse {
|
||||
// return globalObject.throwInvalidArgumentType("hash", "password", "string or TypedArray");
|
||||
// }
|
||||
const owned_hash = JSC.Node.StringOrBuffer.fromJSToOwnedSlice(globalObject, arguments[1], bun.default_allocator) catch |err| {
|
||||
bun.default_allocator.free(owned_password);
|
||||
return err;
|
||||
};
|
||||
|
||||
if (owned_hash.len == 0) {
|
||||
bun.default_allocator.free(owned_password);
|
||||
return JSC.JSPromise.resolvedPromiseValue(globalObject, JSC.JSValue.jsBoolean(false));
|
||||
}
|
||||
|
||||
if (owned_password.len == 0) {
|
||||
bun.default_allocator.free(owned_hash);
|
||||
return JSC.JSPromise.resolvedPromiseValue(globalObject, JSC.JSValue.jsBoolean(false));
|
||||
}
|
||||
|
||||
return verify(globalObject, owned_password, owned_hash, algorithm, false);
|
||||
}
|
||||
|
||||
// Once we have bindings generator, this should be replaced with a generated function
|
||||
pub fn JSPasswordObject__verifySync(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
||||
const arguments_ = callframe.arguments_old(3);
|
||||
const arguments = arguments_.ptr[0..arguments_.len];
|
||||
|
||||
if (arguments.len < 2) {
|
||||
return globalObject.throwNotEnoughArguments("verify", 2, 0);
|
||||
}
|
||||
|
||||
var algorithm: ?PasswordObject.Algorithm = null;
|
||||
|
||||
if (arguments.len > 2 and !arguments[2].isEmptyOrUndefinedOrNull()) {
|
||||
if (!arguments[2].isString()) {
|
||||
return globalObject.throwInvalidArgumentType("verify", "algorithm", "string");
|
||||
}
|
||||
|
||||
const algorithm_string = try arguments[2].getZigString(globalObject);
|
||||
|
||||
algorithm = PasswordObject.Algorithm.label.getWithEql(algorithm_string, JSC.ZigString.eqlComptime) orelse {
|
||||
if (!globalObject.hasException()) {
|
||||
return globalObject.throwInvalidArgumentType("verify", "algorithm", unknown_password_algorithm_message);
|
||||
}
|
||||
return .zero;
|
||||
};
|
||||
}
|
||||
|
||||
var password = try JSC.Node.StringOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[0]) orelse {
|
||||
return globalObject.throwInvalidArgumentType("verify", "password", "string or TypedArray");
|
||||
};
|
||||
|
||||
var hash_ = try JSC.Node.StringOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[1]) orelse {
|
||||
password.deinit();
|
||||
return globalObject.throwInvalidArgumentType("verify", "hash", "string or TypedArray");
|
||||
};
|
||||
|
||||
defer password.deinit();
|
||||
defer hash_.deinit();
|
||||
|
||||
if (hash_.slice().len == 0) {
|
||||
return JSC.JSValue.jsBoolean(false);
|
||||
}
|
||||
|
||||
if (password.slice().len == 0) {
|
||||
return JSC.JSValue.jsBoolean(false);
|
||||
}
|
||||
|
||||
return verify(globalObject, password.slice(), hash_.slice(), algorithm, true);
|
||||
}
|
||||
};
|
||||
|
||||
const std = @import("std");
|
||||
const bun = @import("root").bun;
|
||||
const string = bun.string;
|
||||
const strings = bun.strings;
|
||||
const MutableString = bun.MutableString;
|
||||
const stringZ = bun.stringZ;
|
||||
const default_allocator = bun.default_allocator;
|
||||
const JSC = bun.JSC;
|
||||
const Async = bun.Async;
|
||||
const ZigString = JSC.ZigString;
|
||||
const JSValue = JSC.JSValue;
|
||||
const JSGlobalObject = JSC.JSGlobalObject;
|
||||
const CallFrame = JSC.CallFrame;
|
||||
const assert = bun.assert;
|
||||
|
||||
const unknown_password_algorithm_message = "unknown algorithm, expected one of: \"bcrypt\", \"argon2id\", \"argon2d\", \"argon2i\" (default is \"argon2id\")";
|
||||
@@ -78,3 +78,17 @@ describe("banned words", () => {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe("files that must have comments at the top", () => {
|
||||
const files = ["src/bun.js/api/BunObject.zig"];
|
||||
|
||||
for (const file of files) {
|
||||
test(file, async () => {
|
||||
const joined = path.join(import.meta.dir, "..", "..", file);
|
||||
const content = await Bun.file(joined).text();
|
||||
if (!content.startsWith("//")) {
|
||||
throw new Error(`Please don't add imports to the top of ${file}. Put them at the bottom.`);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user