mirror of
https://github.com/oven-sh/bun
synced 2026-02-04 07:58:54 +00:00
Compare commits
13 Commits
debugger-d
...
plugin/res
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f2095d1c6 | ||
|
|
cfd68a4e9b | ||
|
|
dba07b8675 | ||
|
|
599162ce73 | ||
|
|
ec71e7afe4 | ||
|
|
17bca62df1 | ||
|
|
0c11762c31 | ||
|
|
fe7d5357d8 | ||
|
|
568f170e12 | ||
|
|
c87d65856c | ||
|
|
9b996e702e | ||
|
|
2cb1376a93 | ||
|
|
1f3da24fe0 |
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -2,7 +2,7 @@
|
||||
"git.autoRepositoryDetection": "openEditors",
|
||||
"search.quickOpen.includeSymbols": false,
|
||||
"search.seedWithNearestWord": true,
|
||||
"search.smartCase": true,
|
||||
"search.smartCase": false,
|
||||
"search.exclude": {},
|
||||
"search.followSymlinks": false,
|
||||
"search.useIgnoreFiles": true,
|
||||
|
||||
14
Makefile
14
Makefile
@@ -1085,7 +1085,7 @@ dev-obj-linux:
|
||||
$(ZIG) build obj -Dtarget=x86_64-linux-gnu -Dcpu="$(CPU_TARGET)"
|
||||
|
||||
.PHONY: dev
|
||||
dev: mkdir-dev esm dev-obj bun-link-lld-debug
|
||||
dev: mkdir-dev esm dev-obj link
|
||||
|
||||
mkdir-dev:
|
||||
mkdir -p $(DEBUG_PACKAGE_DIR)
|
||||
@@ -1356,15 +1356,21 @@ mimalloc-wasm:
|
||||
cd $(BUN_DEPS_DIR)/mimalloc; emcmake cmake -DMI_BUILD_SHARED=OFF -DMI_BUILD_STATIC=ON -DMI_BUILD_TESTS=OFF -DMI_BUILD_OBJECT=ON ${MIMALLOC_OVERRIDE_FLAG} -DMI_USE_CXX=ON .; emmake make;
|
||||
cp $(BUN_DEPS_DIR)/mimalloc/$(MIMALLOC_INPUT_PATH) $(BUN_DEPS_OUT_DIR)/$(MIMALLOC_FILE).wasm
|
||||
|
||||
bun-link-lld-debug:
|
||||
# alias for link, incase anyone still types that
|
||||
.PHONY: bun-link-lld-debug
|
||||
bun-link-lld-debug: link
|
||||
|
||||
# link a debug build of bun
|
||||
.PHONY: link
|
||||
link:
|
||||
$(CXX) $(BUN_LLD_FLAGS_DEBUG) $(DEBUG_FLAGS) $(SYMBOLS) \
|
||||
-g \
|
||||
$(DEBUG_BIN)/bun-debug.o \
|
||||
-W \
|
||||
-o $(DEBUG_BIN)/bun-debug
|
||||
@rm -f $(DEBUG_BIN)/bun-debug.o.o 2> /dev/null # workaround for https://github.com/ziglang/zig/issues/14080
|
||||
@rm -f $(DEBUG_BIN)/bun-debug.o.o 2> /dev/null # workaround for https://github.com/ziglang/zig/issues/14080
|
||||
|
||||
bun-link-lld-debug-no-jsc:
|
||||
link-no-jsc:
|
||||
$(CXX) $(BUN_LLD_FLAGS_WITHOUT_JSC) $(SYMBOLS) \
|
||||
-g \
|
||||
$(DEBUG_BIN)/bun-debug.o \
|
||||
|
||||
196
packages/bun-types/bun.d.ts
vendored
196
packages/bun-types/bun.d.ts
vendored
@@ -1012,6 +1012,197 @@ declare module "bun" {
|
||||
// importSource?: string; // default: "react"
|
||||
// };
|
||||
}
|
||||
namespace Password {
|
||||
export type AlgorithmLabel = "bcrypt" | "argon2id" | "argon2d" | "argon2i";
|
||||
|
||||
export interface Argon2Algorithm {
|
||||
algorithm: "argon2id" | "argon2d" | "argon2i";
|
||||
/**
|
||||
* Memory cost, which defines the memory usage, given in kibibytes.
|
||||
*/
|
||||
memoryCost?: number;
|
||||
/**
|
||||
* Defines the amount of computation realized and therefore the execution
|
||||
* time, given in number of iterations.
|
||||
*/
|
||||
timeCost?: number;
|
||||
}
|
||||
|
||||
export interface BCryptAlgorithm {
|
||||
algorithm: "bcrypt";
|
||||
/**
|
||||
* A number between 4 and 31. The default is 10.
|
||||
*/
|
||||
cost?: number;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash and verify passwords using argon2 or bcrypt. The default is argon2.
|
||||
* Password hashing functions are necessarily slow, and this object will
|
||||
* automatically run in a worker thread.
|
||||
*
|
||||
* The underlying implementation of these functions are provided by the Zig
|
||||
* Standard Library. Thanks to @jedisct1 and other Zig constributors for their
|
||||
* work on this.
|
||||
*
|
||||
* ### Example with argon2
|
||||
*
|
||||
* ```ts
|
||||
* import {password} from "bun";
|
||||
*
|
||||
* const hash = await password.hash("hello world");
|
||||
* const verify = await password.verify("hello world", hash);
|
||||
* console.log(verify); // true
|
||||
* ```
|
||||
*
|
||||
* ### Example with bcrypt
|
||||
* ```ts
|
||||
* import {password} from "bun";
|
||||
*
|
||||
* const hash = await password.hash("hello world", "bcrypt");
|
||||
* // algorithm is optional, will be inferred from the hash if not specified
|
||||
* const verify = await password.verify("hello world", hash, "bcrypt");
|
||||
*
|
||||
* console.log(verify); // true
|
||||
* ```
|
||||
*/
|
||||
export const password: {
|
||||
/**
|
||||
* Verify a password against a previously hashed password.
|
||||
*
|
||||
* @returns true if the password matches, false otherwise
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import {password} from "bun";
|
||||
* await password.verify("hey", "$argon2id$v=19$m=65536,t=2,p=1$ddbcyBcbAcagei7wSkZFiouX6TqnUQHmTyS5mxGCzeM$+3OIaFatZ3n6LtMhUlfWbgJyNp7h8/oIsLK+LzZO+WI");
|
||||
* // true
|
||||
* ```
|
||||
*
|
||||
* @throws If the algorithm is specified and does not match the hash
|
||||
* @throws If the algorithm is invalid
|
||||
* @throws if the hash is invalid
|
||||
*
|
||||
*/
|
||||
verify(
|
||||
/**
|
||||
* The password to verify.
|
||||
*
|
||||
* If empty, always returns false
|
||||
*/
|
||||
password: StringOrBuffer,
|
||||
/**
|
||||
* Previously hashed password.
|
||||
* If empty, always returns false
|
||||
*/
|
||||
hash: StringOrBuffer,
|
||||
/**
|
||||
* If not specified, the algorithm will be inferred from the hash.
|
||||
*
|
||||
* If specified and the algorithm does not match the hash, this function
|
||||
* throws an error.
|
||||
*/
|
||||
algorithm?: Password.AlgorithmLabel,
|
||||
): Promise<boolean>;
|
||||
/**
|
||||
* Asynchronously hash a password using argon2 or bcrypt. The default is argon2.
|
||||
*
|
||||
* @returns A promise that resolves to the hashed password
|
||||
*
|
||||
* ## Example with argon2
|
||||
* ```ts
|
||||
* import {password} from "bun";
|
||||
* const hash = await password.hash("hello world");
|
||||
* console.log(hash); // $argon2id$v=1...
|
||||
* const verify = await password.verify("hello world", hash);
|
||||
* ```
|
||||
* ## Example with bcrypt
|
||||
* ```ts
|
||||
* import {password} from "bun";
|
||||
* const hash = await password.hash("hello world", "bcrypt");
|
||||
* console.log(hash); // $2b$10$...
|
||||
* const verify = await password.verify("hello world", hash);
|
||||
* ```
|
||||
*/
|
||||
hash(
|
||||
/**
|
||||
* The password to hash
|
||||
*
|
||||
* If empty, this function throws an error. It is usually a programming
|
||||
* mistake to hash an empty password.
|
||||
*/
|
||||
password: StringOrBuffer,
|
||||
/**
|
||||
* @default "argon2id"
|
||||
*
|
||||
* When using bcrypt, passwords exceeding 72 characters will be SHA512'd before
|
||||
*/
|
||||
algorithm?:
|
||||
| Password.AlgorithmLabel
|
||||
| Password.Argon2Algorithm
|
||||
| Password.BCryptAlgorithm,
|
||||
): Promise<string>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Synchronously hash and verify passwords using argon2 or bcrypt. The default is argon2.
|
||||
* Warning: password hashing is slow, consider using {@link Bun.password}
|
||||
* instead which runs in a worker thread.
|
||||
*
|
||||
* The underlying implementation of these functions are provided by the Zig
|
||||
* Standard Library. Thanks to @jedisct1 and other Zig constributors for their
|
||||
* work on this.
|
||||
*
|
||||
* ### Example with argon2
|
||||
*
|
||||
* ```ts
|
||||
* import {password} from "bun";
|
||||
*
|
||||
* const hash = await password.hash("hello world");
|
||||
* const verify = await password.verify("hello world", hash);
|
||||
* console.log(verify); // true
|
||||
* ```
|
||||
*
|
||||
* ### Example with bcrypt
|
||||
* ```ts
|
||||
* import {password} from "bun";
|
||||
*
|
||||
* const hash = await password.hash("hello world", "bcrypt");
|
||||
* // algorithm is optional, will be inferred from the hash if not specified
|
||||
* const verify = await password.verify("hello world", hash, "bcrypt");
|
||||
*
|
||||
* console.log(verify); // true
|
||||
* ```
|
||||
*/
|
||||
export const passwordSync: {
|
||||
verify(
|
||||
password: StringOrBuffer,
|
||||
hash: StringOrBuffer,
|
||||
/**
|
||||
* If not specified, the algorithm will be inferred from the hash.
|
||||
*/
|
||||
algorithm?: Password.AlgorithmLabel,
|
||||
): boolean;
|
||||
hash(
|
||||
/**
|
||||
* The password to hash
|
||||
*
|
||||
* If empty, this function throws an error. It is usually a programming
|
||||
* mistake to hash an empty password.
|
||||
*/
|
||||
password: StringOrBuffer,
|
||||
/**
|
||||
* @default "argon2id"
|
||||
*
|
||||
* When using bcrypt, passwords exceeding 72 characters will be SHA256'd before
|
||||
*/
|
||||
algorithm?:
|
||||
| Password.AlgorithmLabel
|
||||
| Password.Argon2Algorithm
|
||||
| Password.BCryptAlgorithm,
|
||||
): string;
|
||||
};
|
||||
|
||||
interface BuildArtifact extends Blob {
|
||||
path: string;
|
||||
@@ -2746,7 +2937,10 @@ declare module "bun" {
|
||||
* The kind of import this resolve is for.
|
||||
*/
|
||||
kind: ImportKind;
|
||||
// resolveDir: string;
|
||||
/**
|
||||
* The= directory of the importing module. Commonly used to resolve `args.path` to an absolute path.
|
||||
*/
|
||||
resolveDir: string;
|
||||
// pluginData: any;
|
||||
}
|
||||
|
||||
|
||||
@@ -893,6 +893,7 @@ pub const JSBundler = struct {
|
||||
importer: *const ZigString,
|
||||
context: *anyopaque,
|
||||
u8,
|
||||
resolve_dir: *const ZigString,
|
||||
) void;
|
||||
|
||||
pub fn hasAnyMatches(
|
||||
@@ -949,7 +950,10 @@ pub const JSBundler = struct {
|
||||
ZigString.fromUTF8(namespace);
|
||||
const path_string = ZigString.fromUTF8(path);
|
||||
const importer_string = ZigString.fromUTF8(importer);
|
||||
JSBundlerPlugin__matchOnResolve(globalThis, this, &namespace_string, &path_string, &importer_string, context, @enumToInt(import_record_kind));
|
||||
// TODO: improve this for virtual modules
|
||||
const resolve_dir = std.fs.path.dirname(importer) orelse "/";
|
||||
const resolve_dir_string = ZigString.fromUTF8(resolve_dir);
|
||||
JSBundlerPlugin__matchOnResolve(globalThis, this, &namespace_string, &path_string, &importer_string, context, @enumToInt(import_record_kind), &resolve_dir_string);
|
||||
}
|
||||
|
||||
pub fn addPlugin(
|
||||
|
||||
@@ -1632,6 +1632,803 @@ pub const Crypto = struct {
|
||||
|
||||
return ZigString.fromUTF8(error_message).toErrorInstance(globalThis);
|
||||
}
|
||||
const unknwon_password_algorithm_message = "unknown algorithm, expected one of: \"bcrypt\", \"argon2id\", \"argon2d\", \"argon2i\" (default is \"argon2id\")";
|
||||
|
||||
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) ?Value {
|
||||
if (value.isObject()) {
|
||||
if (value.getTruthy(globalObject, "algorithm")) |algorithm_value| {
|
||||
if (!algorithm_value.isString()) {
|
||||
globalObject.throwInvalidArgumentType("hash", "algorithm", "string");
|
||||
return null;
|
||||
}
|
||||
|
||||
const algorithm_string = algorithm_value.getZigString(globalObject);
|
||||
|
||||
switch (PasswordObject.Algorithm.label.getWithEql(algorithm_string, JSC.ZigString.eqlComptime) orelse {
|
||||
globalObject.throwInvalidArgumentType("hash", "algorithm", unknwon_password_algorithm_message);
|
||||
return null;
|
||||
}) {
|
||||
.bcrypt => {
|
||||
var algorithm = PasswordObject.Algorithm.Value{
|
||||
.bcrypt = PasswordObject.Algorithm.Value.bcrpyt_default,
|
||||
};
|
||||
|
||||
if (value.getTruthy(globalObject, "cost")) |rounds_value| {
|
||||
if (!rounds_value.isNumber()) {
|
||||
globalObject.throwInvalidArgumentType("hash", "cost", "number");
|
||||
return null;
|
||||
}
|
||||
|
||||
const rounds = rounds_value.coerce(i32, globalObject);
|
||||
|
||||
if (rounds < 4 or rounds > 31) {
|
||||
globalObject.throwInvalidArguments("Rounds must be between 4 and 31", .{});
|
||||
return null;
|
||||
}
|
||||
|
||||
algorithm.bcrypt = @intCast(u6, rounds);
|
||||
}
|
||||
|
||||
return algorithm;
|
||||
},
|
||||
inline .argon2id, .argon2d, .argon2i => |tag| {
|
||||
var argon = Algorithm.Argon2Params{};
|
||||
|
||||
if (value.getTruthy(globalObject, "timeCost")) |time_value| {
|
||||
if (!time_value.isNumber()) {
|
||||
globalObject.throwInvalidArgumentType("hash", "timeCost", "number");
|
||||
return null;
|
||||
}
|
||||
|
||||
const time_cost = time_value.coerce(i32, globalObject);
|
||||
|
||||
if (time_cost < 1) {
|
||||
globalObject.throwInvalidArguments("Time cost must be greater than 0", .{});
|
||||
return null;
|
||||
}
|
||||
|
||||
argon.time_cost = @intCast(u32, time_cost);
|
||||
}
|
||||
|
||||
if (value.getTruthy(globalObject, "memoryCost")) |memory_value| {
|
||||
if (!memory_value.isNumber()) {
|
||||
globalObject.throwInvalidArgumentType("hash", "memoryCost", "number");
|
||||
return null;
|
||||
}
|
||||
|
||||
const memory_cost = memory_value.coerce(i32, globalObject);
|
||||
|
||||
if (memory_cost < 1) {
|
||||
globalObject.throwInvalidArguments("Memory cost must be greater than 0", .{});
|
||||
return null;
|
||||
}
|
||||
|
||||
argon.memory_cost = @intCast(u32, memory_cost);
|
||||
}
|
||||
|
||||
return @unionInit(Algorithm.Value, @tagName(tag), argon);
|
||||
},
|
||||
}
|
||||
|
||||
unreachable;
|
||||
} else {
|
||||
globalObject.throwInvalidArgumentType("hash", "options.algorithm", "string");
|
||||
return null;
|
||||
}
|
||||
} else if (value.isString()) {
|
||||
const algorithm_string = value.getZigString(globalObject);
|
||||
|
||||
switch (PasswordObject.Algorithm.label.getWithEql(algorithm_string, JSC.ZigString.eqlComptime) orelse {
|
||||
globalObject.throwInvalidArgumentType("hash", "algorithm", unknwon_password_algorithm_message);
|
||||
return null;
|
||||
}) {
|
||||
.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 {
|
||||
globalObject.throwInvalidArgumentType("hash", "algorithm", "string");
|
||||
return null;
|
||||
}
|
||||
|
||||
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_256 = bun.sha.SHA512.init();
|
||||
sha_256.update(password);
|
||||
sha_256.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 },
|
||||
.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 => {
|
||||
pwhash.bcrypt.strVerify(previous_hash, password, .{ .allocator = allocator }) 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, sync: bool) JSC.JSValue {
|
||||
var object = JSValue.createEmptyObject(globalObject, 2);
|
||||
object.put(
|
||||
globalObject,
|
||||
ZigString.static("hash"),
|
||||
if (!sync)
|
||||
JSC.NewFunction(globalObject, ZigString.static("hash"), 2, JSPasswordObject__hash, false)
|
||||
else
|
||||
JSC.NewFunction(globalObject, ZigString.static("hash"), 2, JSPasswordObject__hashSync, false),
|
||||
);
|
||||
object.put(
|
||||
globalObject,
|
||||
ZigString.static("verify"),
|
||||
if (!sync)
|
||||
JSC.NewFunction(globalObject, ZigString.static("verify"), 2, JSPasswordObject__verify, false)
|
||||
else
|
||||
JSC.NewFunction(globalObject, ZigString.static("verify"), 2, JSPasswordObject__verifySync, false),
|
||||
);
|
||||
return object;
|
||||
}
|
||||
|
||||
const HashJob = struct {
|
||||
algorithm: PasswordObject.Algorithm.Value,
|
||||
password: []const u8,
|
||||
promise: JSC.JSPromise.Strong,
|
||||
event_loop: *JSC.EventLoop,
|
||||
global: *JSC.JSGlobalObject,
|
||||
ref: JSC.PollRef = .{},
|
||||
task: JSC.WorkPoolTask = .{ .callback = &run },
|
||||
|
||||
pub const Result = struct {
|
||||
value: Value,
|
||||
ref: JSC.PollRef = .{},
|
||||
|
||||
task: JSC.AnyTask = undefined,
|
||||
promise: JSC.JSPromise.Strong,
|
||||
global: *JSC.JSGlobalObject,
|
||||
|
||||
pub const Value = union(enum) {
|
||||
err: PasswordObject.HashError,
|
||||
hash: []const u8,
|
||||
|
||||
pub fn toErrorInstance(this: Value, globalObject: *JSC.JSGlobalObject) JSC.JSValue {
|
||||
var error_code = std.fmt.allocPrint(bun.default_allocator, "PASSWORD_{}", .{PascalToUpperUnderscoreCaseFormatter{ .input = @errorName(this.err) }}) catch @panic("out of memory");
|
||||
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).toValueGC(globalObject));
|
||||
return instance;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn runFromJS(this: *Result) void {
|
||||
var promise = this.promise;
|
||||
this.promise = .{};
|
||||
this.ref.unref(this.global.bunVM());
|
||||
var global = this.global;
|
||||
switch (this.value) {
|
||||
.err => {
|
||||
const error_instance = this.value.toErrorInstance(global);
|
||||
bun.default_allocator.destroy(this);
|
||||
promise.reject(global, error_instance);
|
||||
},
|
||||
.hash => |value| {
|
||||
const js_string = JSC.ZigString.init(value).toValueGC(global);
|
||||
bun.default_allocator.destroy(this);
|
||||
promise.resolve(global, js_string);
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub fn deinit(this: *HashJob) void {
|
||||
this.ref = .{};
|
||||
this.promise.strong.deinit();
|
||||
bun.default_allocator.free(this.password);
|
||||
bun.default_allocator.destroy(this);
|
||||
}
|
||||
|
||||
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 = @fieldParentPtr(HashJob, "task", task);
|
||||
|
||||
var result = bun.default_allocator.create(Result) catch @panic("out of memory");
|
||||
result.* = Result{
|
||||
.value = getValue(this.password, this.algorithm),
|
||||
.task = JSC.AnyTask.New(Result, Result.runFromJS).init(result),
|
||||
.promise = this.promise,
|
||||
.global = this.global,
|
||||
.ref = this.ref,
|
||||
};
|
||||
this.ref = .{};
|
||||
this.promise.strong = .{};
|
||||
|
||||
var concurrent_task = bun.default_allocator.create(JSC.ConcurrentTask) catch @panic("out of memory");
|
||||
concurrent_task.* = JSC.ConcurrentTask{
|
||||
.task = JSC.Task.init(&result.task),
|
||||
.auto_delete = true,
|
||||
};
|
||||
this.event_loop.enqueueTaskConcurrent(concurrent_task);
|
||||
this.deinit();
|
||||
}
|
||||
};
|
||||
pub fn hash(
|
||||
globalObject: *JSC.JSGlobalObject,
|
||||
password: []const u8,
|
||||
algorithm: PasswordObject.Algorithm.Value,
|
||||
comptime sync: bool,
|
||||
) JSC.JSValue {
|
||||
std.debug.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);
|
||||
globalObject.throwValue(error_instance);
|
||||
},
|
||||
.hash => |h| {
|
||||
return JSC.ZigString.init(h).toValueGC(globalObject);
|
||||
},
|
||||
}
|
||||
|
||||
unreachable;
|
||||
}
|
||||
|
||||
var job = bun.default_allocator.create(HashJob) catch @panic("out of memory");
|
||||
var promise = JSC.JSPromise.Strong.init(globalObject);
|
||||
|
||||
job.* = HashJob{
|
||||
.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,
|
||||
) JSC.JSValue {
|
||||
std.debug.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);
|
||||
globalObject.throwValue(error_instance);
|
||||
return JSC.JSValue.undefined;
|
||||
},
|
||||
.pass => |pass| {
|
||||
return JSC.JSValue.jsBoolean(pass);
|
||||
},
|
||||
}
|
||||
|
||||
unreachable;
|
||||
}
|
||||
|
||||
var job = bun.default_allocator.create(VerifyJob) catch @panic("out of memory");
|
||||
var promise = JSC.JSPromise.Strong.init(globalObject);
|
||||
|
||||
job.* = VerifyJob{
|
||||
.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 export fn JSPasswordObject__hash(
|
||||
globalObject: *JSC.JSGlobalObject,
|
||||
callframe: *JSC.CallFrame,
|
||||
) callconv(.C) JSC.JSValue {
|
||||
const arguments_ = callframe.arguments(2);
|
||||
const arguments = arguments_.ptr[0..arguments_.len];
|
||||
|
||||
if (arguments.len < 1) {
|
||||
globalObject.throwNotEnoughArguments("hash", 1, 0);
|
||||
return JSC.JSValue.undefined;
|
||||
}
|
||||
|
||||
var algorithm = PasswordObject.Algorithm.Value.default;
|
||||
|
||||
if (arguments.len > 1 and !arguments[1].isEmptyOrUndefinedOrNull()) {
|
||||
algorithm = PasswordObject.Algorithm.Value.fromJS(globalObject, arguments[1]) orelse
|
||||
return JSC.JSValue.undefined;
|
||||
}
|
||||
|
||||
var string_or_buffer = JSC.Node.SliceOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[0]) orelse {
|
||||
globalObject.throwInvalidArgumentType("hash", "password", "string or TypedArray");
|
||||
return JSC.JSValue.undefined;
|
||||
};
|
||||
|
||||
if (string_or_buffer.slice().len == 0) {
|
||||
globalObject.throwInvalidArguments("password must not be empty", .{});
|
||||
string_or_buffer.deinit();
|
||||
return JSC.JSValue.undefined;
|
||||
}
|
||||
|
||||
string_or_buffer.ensureCloned(bun.default_allocator) catch {
|
||||
globalObject.throwOutOfMemory();
|
||||
return JSC.JSValue.undefined;
|
||||
};
|
||||
|
||||
return hash(globalObject, string_or_buffer.slice(), algorithm, false);
|
||||
}
|
||||
|
||||
// Once we have bindings generator, this should be replaced with a generated function
|
||||
pub export fn JSPasswordObject__hashSync(
|
||||
globalObject: *JSC.JSGlobalObject,
|
||||
callframe: *JSC.CallFrame,
|
||||
) callconv(.C) JSC.JSValue {
|
||||
const arguments_ = callframe.arguments(2);
|
||||
const arguments = arguments_.ptr[0..arguments_.len];
|
||||
|
||||
if (arguments.len < 1) {
|
||||
globalObject.throwNotEnoughArguments("hash", 1, 0);
|
||||
return JSC.JSValue.undefined;
|
||||
}
|
||||
|
||||
var algorithm = PasswordObject.Algorithm.Value.default;
|
||||
|
||||
if (arguments.len > 1 and !arguments[1].isEmptyOrUndefinedOrNull()) {
|
||||
algorithm = PasswordObject.Algorithm.Value.fromJS(globalObject, arguments[1]) orelse
|
||||
return JSC.JSValue.undefined;
|
||||
}
|
||||
|
||||
var string_or_buffer = JSC.Node.SliceOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[0]) orelse {
|
||||
globalObject.throwInvalidArgumentType("hash", "password", "string or TypedArray");
|
||||
return JSC.JSValue.undefined;
|
||||
};
|
||||
|
||||
if (string_or_buffer.slice().len == 0) {
|
||||
globalObject.throwInvalidArguments("password must not be empty", .{});
|
||||
string_or_buffer.deinit();
|
||||
return JSC.JSValue.undefined;
|
||||
}
|
||||
|
||||
string_or_buffer.ensureCloned(bun.default_allocator) catch {
|
||||
globalObject.throwOutOfMemory();
|
||||
return JSC.JSValue.undefined;
|
||||
};
|
||||
defer string_or_buffer.deinit();
|
||||
|
||||
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: JSC.PollRef = .{},
|
||||
task: JSC.WorkPoolTask = .{ .callback = &run },
|
||||
|
||||
pub const Result = struct {
|
||||
value: Value,
|
||||
ref: JSC.PollRef = .{},
|
||||
|
||||
task: JSC.AnyTask = undefined,
|
||||
promise: JSC.JSPromise.Strong,
|
||||
global: *JSC.JSGlobalObject,
|
||||
|
||||
pub const Value = union(enum) {
|
||||
err: PasswordObject.HashError,
|
||||
pass: bool,
|
||||
|
||||
pub fn toErrorInstance(this: Value, globalObject: *JSC.JSGlobalObject) JSC.JSValue {
|
||||
var error_code = std.fmt.allocPrint(bun.default_allocator, "PASSWORD{}", .{PascalToUpperUnderscoreCaseFormatter{ .input = @errorName(this.err) }}) catch @panic("out of memory");
|
||||
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).toValueGC(globalObject));
|
||||
return instance;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn runFromJS(this: *Result) void {
|
||||
var promise = this.promise;
|
||||
this.promise = .{};
|
||||
this.ref.unref(this.global.bunVM());
|
||||
var global = this.global;
|
||||
switch (this.value) {
|
||||
.err => {
|
||||
const error_instance = this.value.toErrorInstance(global);
|
||||
bun.default_allocator.destroy(this);
|
||||
promise.reject(global, error_instance);
|
||||
},
|
||||
.pass => |pass| {
|
||||
bun.default_allocator.destroy(this);
|
||||
promise.resolve(global, JSC.JSValue.jsBoolean(pass));
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub fn deinit(this: *VerifyJob) void {
|
||||
this.ref = .{};
|
||||
this.promise.strong.deinit();
|
||||
bun.default_allocator.free(this.password);
|
||||
bun.default_allocator.free(this.prev_hash);
|
||||
bun.default_allocator.destroy(this);
|
||||
}
|
||||
|
||||
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 = @fieldParentPtr(VerifyJob, "task", task);
|
||||
|
||||
var result = bun.default_allocator.create(Result) catch @panic("out of memory");
|
||||
result.* = Result{
|
||||
.value = getValue(this.password, this.prev_hash, this.algorithm),
|
||||
.task = JSC.AnyTask.New(Result, Result.runFromJS).init(result),
|
||||
.promise = this.promise,
|
||||
.global = this.global,
|
||||
.ref = this.ref,
|
||||
};
|
||||
this.ref = .{};
|
||||
this.promise.strong = .{};
|
||||
|
||||
var concurrent_task = bun.default_allocator.create(JSC.ConcurrentTask) catch @panic("out of memory");
|
||||
concurrent_task.* = JSC.ConcurrentTask{
|
||||
.task = JSC.Task.init(&result.task),
|
||||
.auto_delete = true,
|
||||
};
|
||||
this.event_loop.enqueueTaskConcurrent(concurrent_task);
|
||||
this.deinit();
|
||||
}
|
||||
};
|
||||
|
||||
// Once we have bindings generator, this should be replaced with a generated function
|
||||
pub export fn JSPasswordObject__verify(
|
||||
globalObject: *JSC.JSGlobalObject,
|
||||
callframe: *JSC.CallFrame,
|
||||
) callconv(.C) JSC.JSValue {
|
||||
const arguments_ = callframe.arguments(3);
|
||||
const arguments = arguments_.ptr[0..arguments_.len];
|
||||
|
||||
if (arguments.len < 2) {
|
||||
globalObject.throwNotEnoughArguments("verify", 2, 0);
|
||||
return JSC.JSValue.undefined;
|
||||
}
|
||||
|
||||
var algorithm: ?PasswordObject.Algorithm = null;
|
||||
|
||||
if (arguments.len > 2 and !arguments[2].isEmptyOrUndefinedOrNull()) {
|
||||
if (!arguments[2].isString()) {
|
||||
globalObject.throwInvalidArgumentType("verify", "algorithm", "string");
|
||||
return JSC.JSValue.undefined;
|
||||
}
|
||||
|
||||
const algorithm_string = arguments[2].getZigString(globalObject);
|
||||
|
||||
algorithm = PasswordObject.Algorithm.label.getWithEql(algorithm_string, JSC.ZigString.eqlComptime) orelse {
|
||||
globalObject.throwInvalidArgumentType("verify", "algorithm", unknwon_password_algorithm_message);
|
||||
return JSC.JSValue.undefined;
|
||||
};
|
||||
}
|
||||
|
||||
var password = JSC.Node.SliceOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[0]) orelse {
|
||||
globalObject.throwInvalidArgumentType("verify", "password", "string or TypedArray");
|
||||
return JSC.JSValue.undefined;
|
||||
};
|
||||
|
||||
var hash_ = JSC.Node.SliceOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[1]) orelse {
|
||||
password.deinit();
|
||||
globalObject.throwInvalidArgumentType("verify", "hash", "string or TypedArray");
|
||||
return JSC.JSValue.undefined;
|
||||
};
|
||||
|
||||
if (hash_.slice().len == 0) {
|
||||
password.deinit();
|
||||
return JSC.JSPromise.resolvedPromiseValue(globalObject, JSC.JSValue.jsBoolean(false));
|
||||
}
|
||||
|
||||
if (password.slice().len == 0) {
|
||||
hash_.deinit();
|
||||
return JSC.JSPromise.resolvedPromiseValue(globalObject, JSC.JSValue.jsBoolean(false));
|
||||
}
|
||||
|
||||
password.ensureCloned(bun.default_allocator) catch {
|
||||
hash_.deinit();
|
||||
globalObject.throwOutOfMemory();
|
||||
return JSC.JSValue.undefined;
|
||||
};
|
||||
|
||||
hash_.ensureCloned(bun.default_allocator) catch {
|
||||
password.deinit();
|
||||
globalObject.throwOutOfMemory();
|
||||
return JSC.JSValue.undefined;
|
||||
};
|
||||
|
||||
return verify(globalObject, password.slice(), hash_.slice(), algorithm, false);
|
||||
}
|
||||
|
||||
// Once we have bindings generator, this should be replaced with a generated function
|
||||
pub export fn JSPasswordObject__verifySync(
|
||||
globalObject: *JSC.JSGlobalObject,
|
||||
callframe: *JSC.CallFrame,
|
||||
) callconv(.C) JSC.JSValue {
|
||||
const arguments_ = callframe.arguments(3);
|
||||
const arguments = arguments_.ptr[0..arguments_.len];
|
||||
|
||||
if (arguments.len < 2) {
|
||||
globalObject.throwNotEnoughArguments("verify", 2, 0);
|
||||
return JSC.JSValue.undefined;
|
||||
}
|
||||
|
||||
var algorithm: ?PasswordObject.Algorithm = null;
|
||||
|
||||
if (arguments.len > 2 and !arguments[2].isEmptyOrUndefinedOrNull()) {
|
||||
if (!arguments[2].isString()) {
|
||||
globalObject.throwInvalidArgumentType("verify", "algorithm", "string");
|
||||
return JSC.JSValue.undefined;
|
||||
}
|
||||
|
||||
const algorithm_string = arguments[2].getZigString(globalObject);
|
||||
|
||||
algorithm = PasswordObject.Algorithm.label.getWithEql(algorithm_string, JSC.ZigString.eqlComptime) orelse {
|
||||
globalObject.throwInvalidArgumentType("verify", "algorithm", unknwon_password_algorithm_message);
|
||||
return JSC.JSValue.undefined;
|
||||
};
|
||||
}
|
||||
|
||||
var password = JSC.Node.SliceOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[0]) orelse {
|
||||
globalObject.throwInvalidArgumentType("verify", "password", "string or TypedArray");
|
||||
return JSC.JSValue.undefined;
|
||||
};
|
||||
|
||||
var hash_ = JSC.Node.SliceOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[1]) orelse {
|
||||
password.deinit();
|
||||
globalObject.throwInvalidArgumentType("verify", "hash", "string or TypedArray");
|
||||
return JSC.JSValue.undefined;
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
pub const CryptoHasher = struct {
|
||||
evp: EVP = undefined,
|
||||
|
||||
@@ -4306,3 +5103,9 @@ pub const JSZlib = struct {
|
||||
};
|
||||
|
||||
pub usingnamespace @import("./bun/subprocess.zig");
|
||||
|
||||
comptime {
|
||||
if (!JSC.is_bindgen) {
|
||||
_ = Crypto.JSPasswordObject.JSPasswordObject__create;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,6 +136,8 @@ pub const ServerConfig = struct {
|
||||
|
||||
websocket: ?WebSocketServer = null,
|
||||
|
||||
inspector: bool = false,
|
||||
|
||||
pub const SSLConfig = struct {
|
||||
server_name: [*c]const u8 = null,
|
||||
|
||||
@@ -741,7 +743,19 @@ pub const ServerConfig = struct {
|
||||
}
|
||||
|
||||
if (arg.get(global, "development")) |dev| {
|
||||
args.development = dev.toBoolean();
|
||||
args.development = dev.coerce(bool, global);
|
||||
}
|
||||
|
||||
if (arg.get(global, "inspector")) |inspector| {
|
||||
args.inspector = inspector.coerce(bool, global);
|
||||
|
||||
if (args.inspector and !args.development) {
|
||||
JSC.throwInvalidArguments("Cannot enable inspector in production. Please set development: true in Bun.serve()", .{}, global, exception);
|
||||
if (args.ssl_config) |*conf| {
|
||||
conf.deinit();
|
||||
}
|
||||
return args;
|
||||
}
|
||||
}
|
||||
|
||||
if (arg.getTruthy(global, "tls")) |tls| {
|
||||
@@ -5239,6 +5253,10 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
|
||||
|
||||
if (comptime debug_mode) {
|
||||
this.app.get("/bun:info", *ThisServer, this, onBunInfoRequest);
|
||||
if (this.config.inspector) {
|
||||
Bun__addInspector(ssl_enabled, this.app, this.globalThis);
|
||||
}
|
||||
|
||||
this.app.get("/src:/*", *ThisServer, this, onSrcRequest);
|
||||
}
|
||||
|
||||
@@ -5290,3 +5308,5 @@ pub const AnyServer = union(enum) {
|
||||
};
|
||||
|
||||
const welcome_page_html_gz = @embedFile("welcome-page.html.gz");
|
||||
|
||||
extern fn Bun__addInspector(bool, *anyopaque, *JSC.JSGlobalObject) void;
|
||||
|
||||
184
src/bun.js/bindings/BunInspector.cpp
Normal file
184
src/bun.js/bindings/BunInspector.cpp
Normal file
@@ -0,0 +1,184 @@
|
||||
#include "root.h"
|
||||
#include <uws/src/App.h>
|
||||
|
||||
#include <JavaScriptCore/InspectorFrontendChannel.h>
|
||||
#include <JavaScriptCore/JSGlobalObjectDebuggable.h>
|
||||
|
||||
namespace Bun {
|
||||
using namespace JSC;
|
||||
template<bool isSSL>
|
||||
class BunInspectorConnection : public Inspector::FrontendChannel {
|
||||
public:
|
||||
using BunInspectorSocket = uWS::WebSocket<isSSL, true, BunInspectorConnection*>;
|
||||
|
||||
BunInspectorConnection(BunInspectorSocket* ws, JSC::JSGlobalObject* globalObject)
|
||||
: ws(ws)
|
||||
, globalObject(globalObject)
|
||||
, pendingMessages()
|
||||
{
|
||||
}
|
||||
|
||||
~BunInspectorConnection()
|
||||
{
|
||||
}
|
||||
|
||||
Inspector::FrontendChannel::ConnectionType connectionType() const override
|
||||
{
|
||||
return Inspector::FrontendChannel::ConnectionType::Remote;
|
||||
}
|
||||
|
||||
void onOpen(JSC::JSGlobalObject* globalObject)
|
||||
{
|
||||
this->globalObject = globalObject;
|
||||
this->globalObject->inspectorDebuggable().connect(*this);
|
||||
}
|
||||
|
||||
void onClose()
|
||||
{
|
||||
this->globalObject->inspectorDebuggable().disconnect(*this);
|
||||
this->pendingMessages.clear();
|
||||
}
|
||||
|
||||
void sendMessageToFrontend(const String& message) override
|
||||
{
|
||||
send(message);
|
||||
}
|
||||
|
||||
void send(const WTF::String& message)
|
||||
{
|
||||
if (ws->getBufferedAmount() == 0) {
|
||||
WTF::CString messageCString = message.utf8();
|
||||
ws->send(std::string_view { messageCString.data(), messageCString.length() }, uWS::OpCode::TEXT);
|
||||
} else {
|
||||
pendingMessages.append(message);
|
||||
}
|
||||
}
|
||||
|
||||
void onMessage(std::string_view message)
|
||||
{
|
||||
WTF::String messageString = WTF::String::fromUTF8(message.data(), message.length());
|
||||
this->globalObject->inspectorDebuggable().dispatchMessageFromRemote(WTFMove(messageString));
|
||||
}
|
||||
|
||||
void drain()
|
||||
{
|
||||
if (pendingMessages.size() == 0)
|
||||
return;
|
||||
|
||||
if (ws->getBufferedAmount() == 0) {
|
||||
ws->cork([&]() {
|
||||
for (auto& message : pendingMessages) {
|
||||
WTF::CString messageCString = message.utf8();
|
||||
ws->send(std::string_view { messageCString.data(), messageCString.length() }, uWS::OpCode::TEXT);
|
||||
}
|
||||
pendingMessages.clear();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
WTF::Vector<WTF::String> pendingMessages;
|
||||
JSC::JSGlobalObject* globalObject;
|
||||
BunInspectorSocket* ws;
|
||||
};
|
||||
|
||||
using BunInspectorConnectionNoSSL = BunInspectorConnection<false>;
|
||||
using SSLBunInspectorConnection = BunInspectorConnection<true>;
|
||||
|
||||
template<bool isSSL>
|
||||
static void addInspector(void* app, JSC::JSGlobalObject* globalObject)
|
||||
{
|
||||
if constexpr (isSSL) {
|
||||
auto handler = uWS::SSLApp::WebSocketBehavior<SSLBunInspectorConnection*> {
|
||||
/* Settings */
|
||||
.compression = uWS::DISABLED,
|
||||
.maxPayloadLength = 16 * 1024 * 1024,
|
||||
.idleTimeout = 960,
|
||||
.maxBackpressure = 16 * 1024 * 1024,
|
||||
.closeOnBackpressureLimit = false,
|
||||
.resetIdleTimeoutOnSend = true,
|
||||
.sendPingsAutomatically = true,
|
||||
/* Handlers */
|
||||
.upgrade = nullptr,
|
||||
.open = [globalObject](auto* ws) {
|
||||
globalObject->setInspectable(true);
|
||||
*ws->getUserData() = new SSLBunInspectorConnection(ws, globalObject);
|
||||
SSLBunInspectorConnection* inspector = *ws->getUserData();
|
||||
inspector->onOpen(globalObject);
|
||||
//
|
||||
},
|
||||
.message = [](auto* ws, std::string_view message, uWS::OpCode opCode) {
|
||||
SSLBunInspectorConnection* inspector = *(SSLBunInspectorConnection**)ws->getUserData();
|
||||
inspector->onMessage(message);
|
||||
//
|
||||
},
|
||||
.drain = [](auto* ws) {
|
||||
SSLBunInspectorConnection* inspector = *(SSLBunInspectorConnection**)ws->getUserData();
|
||||
inspector->drain();
|
||||
//
|
||||
},
|
||||
.ping = [](auto* /*ws*/, std::string_view) {
|
||||
/* Not implemented yet */ },
|
||||
.pong = [](auto* /*ws*/, std::string_view) {
|
||||
/* Not implemented yet */ },
|
||||
|
||||
.close = [](auto* ws, int /*code*/, std::string_view /*message*/) {
|
||||
SSLBunInspectorConnection* inspector = *(SSLBunInspectorConnection**)ws->getUserData();
|
||||
inspector->onClose();
|
||||
delete inspector; }
|
||||
};
|
||||
|
||||
((uWS::SSLApp*)app)->ws<SSLBunInspectorConnection*>("/bun:inspect", std::move(handler));
|
||||
} else {
|
||||
|
||||
auto handler = uWS::App::WebSocketBehavior<BunInspectorConnectionNoSSL*> {
|
||||
/* Settings */
|
||||
.compression = uWS::DISABLED,
|
||||
.maxPayloadLength = 16 * 1024 * 1024,
|
||||
.idleTimeout = 960,
|
||||
.maxBackpressure = 16 * 1024 * 1024,
|
||||
.closeOnBackpressureLimit = false,
|
||||
.resetIdleTimeoutOnSend = true,
|
||||
.sendPingsAutomatically = true,
|
||||
/* Handlers */
|
||||
.upgrade = nullptr,
|
||||
.open = [globalObject](auto* ws) {
|
||||
globalObject->setInspectable(true);
|
||||
*ws->getUserData() = new BunInspectorConnectionNoSSL(ws, globalObject);
|
||||
BunInspectorConnectionNoSSL* inspector = *ws->getUserData();
|
||||
inspector->onOpen(globalObject);
|
||||
//
|
||||
},
|
||||
.message = [](auto* ws, std::string_view message, uWS::OpCode opCode) {
|
||||
BunInspectorConnectionNoSSL* inspector = *(BunInspectorConnectionNoSSL**)ws->getUserData();
|
||||
inspector->onMessage(message);
|
||||
//
|
||||
},
|
||||
.drain = [](auto* ws) {
|
||||
BunInspectorConnectionNoSSL* inspector = *(BunInspectorConnectionNoSSL**)ws->getUserData();
|
||||
inspector->drain();
|
||||
//
|
||||
},
|
||||
.ping = [](auto* /*ws*/, std::string_view) {
|
||||
/* Not implemented yet */ },
|
||||
.pong = [](auto* /*ws*/, std::string_view) {
|
||||
/* Not implemented yet */ },
|
||||
|
||||
.close = [](auto* ws, int /*code*/, std::string_view /*message*/) {
|
||||
BunInspectorConnectionNoSSL* inspector = *(BunInspectorConnectionNoSSL**)ws->getUserData();
|
||||
inspector->onClose();
|
||||
delete inspector; }
|
||||
};
|
||||
|
||||
((uWS::App*)app)->ws<BunInspectorConnectionNoSSL*>("/bun:inspect", std::move(handler));
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void Bun__addInspector(bool isSSL, void* app, JSC::JSGlobalObject* globalObject)
|
||||
{
|
||||
if (isSSL) {
|
||||
addInspector<true>((uWS::TemplatedApp<true>*)app, globalObject);
|
||||
} else {
|
||||
addInspector<false>((uWS::TemplatedApp<false>*)app, globalObject);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -315,7 +315,7 @@ extern "C" void JSBundlerPlugin__matchOnLoad(JSC::JSGlobalObject* globalObject,
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void JSBundlerPlugin__matchOnResolve(JSC::JSGlobalObject* globalObject, Bun::JSBundlerPlugin* plugin, const ZigString* namespaceString, const ZigString* path, const ZigString* importer, void* context, uint8_t kindId)
|
||||
extern "C" void JSBundlerPlugin__matchOnResolve(JSC::JSGlobalObject* globalObject, Bun::JSBundlerPlugin* plugin, const ZigString* namespaceString, const ZigString* path, const ZigString* importer, void* context, uint8_t kindId, const ZigString* resolveDir)
|
||||
{
|
||||
WTF::String namespaceStringStr = namespaceString ? Zig::toStringCopy(*namespaceString) : WTF::String("file"_s);
|
||||
if (namespaceStringStr.length() == 0) {
|
||||
@@ -323,6 +323,7 @@ extern "C" void JSBundlerPlugin__matchOnResolve(JSC::JSGlobalObject* globalObjec
|
||||
}
|
||||
WTF::String pathStr = path ? Zig::toStringCopy(*path) : WTF::String();
|
||||
WTF::String importerStr = importer ? Zig::toStringCopy(*importer) : WTF::String();
|
||||
WTF::String resolveDirStr = resolveDir ? Zig::toStringCopy(*resolveDir) : WTF::String();
|
||||
auto& vm = globalObject->vm();
|
||||
|
||||
JSFunction* function = plugin->onResolveFunction.get(plugin);
|
||||
@@ -341,6 +342,7 @@ extern "C" void JSBundlerPlugin__matchOnResolve(JSC::JSGlobalObject* globalObjec
|
||||
arguments.append(JSC::jsString(vm, importerStr));
|
||||
arguments.append(WRAP_BUNDLER_PLUGIN(context));
|
||||
arguments.append(JSC::jsNumber(kindId));
|
||||
arguments.append(JSC::jsString(vm, resolveDirStr));
|
||||
|
||||
auto result = call(globalObject, function, callData, plugin, arguments);
|
||||
|
||||
|
||||
@@ -361,6 +361,17 @@ extern "C" bool Zig__GlobalObject__resetModuleRegistryMap(JSC__JSGlobalObject* g
|
||||
return true;
|
||||
}
|
||||
|
||||
#define BUN_LAZY_GETTER_FN_NAME(GetterName) BunLazyGetter##GetterName##_getter
|
||||
|
||||
#define DEFINE_BUN_LAZY_GETTER(GetterName, __propertyName) \
|
||||
JSC_DEFINE_CUSTOM_GETTER(GetterName, \
|
||||
(JSC::JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, \
|
||||
JSC::PropertyName)) \
|
||||
{ \
|
||||
Zig::GlobalObject* thisObject = JSC::jsCast<Zig::GlobalObject*>(lexicalGlobalObject); \
|
||||
return JSC::JSValue::encode(thisObject->__propertyName()); \
|
||||
}
|
||||
|
||||
#define GENERATED_CONSTRUCTOR_GETTER(ConstructorName) \
|
||||
JSC_DECLARE_CUSTOM_GETTER(ConstructorName##_getter); \
|
||||
JSC_DEFINE_CUSTOM_GETTER(ConstructorName##_getter, \
|
||||
@@ -2492,6 +2503,7 @@ JSC::JSValue GlobalObject::formatStackTrace(JSC::VM& vm, JSC::JSGlobalObject* le
|
||||
}
|
||||
|
||||
extern "C" void Bun__remapStackFramePositions(JSC::JSGlobalObject*, ZigStackFrame*, size_t);
|
||||
extern "C" EncodedJSValue JSPasswordObject__create(JSC::JSGlobalObject*, bool);
|
||||
|
||||
JSC_DECLARE_HOST_FUNCTION(errorConstructorFuncCaptureStackTrace);
|
||||
JSC_DEFINE_HOST_FUNCTION(errorConstructorFuncCaptureStackTrace, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame))
|
||||
@@ -2599,6 +2611,24 @@ void GlobalObject::finishCreation(VM& vm)
|
||||
init.set(result.toObject(globalObject));
|
||||
});
|
||||
|
||||
m_lazyPasswordObject.initLater(
|
||||
[](const Initializer<JSObject>& init) {
|
||||
JSC::VM& vm = init.vm;
|
||||
JSC::JSGlobalObject* globalObject = init.owner;
|
||||
|
||||
JSValue result = JSValue::decode(JSPasswordObject__create(globalObject, false));
|
||||
init.set(result.toObject(globalObject));
|
||||
});
|
||||
|
||||
m_lazyPasswordSyncObject.initLater(
|
||||
[](const Initializer<JSObject>& init) {
|
||||
JSC::VM& vm = init.vm;
|
||||
JSC::JSGlobalObject* globalObject = init.owner;
|
||||
|
||||
JSValue result = JSValue::decode(JSPasswordObject__create(globalObject, true));
|
||||
init.set(result.toObject(globalObject));
|
||||
});
|
||||
|
||||
m_lazyPreloadTestModuleObject.initLater(
|
||||
[](const Initializer<JSObject>& init) {
|
||||
JSC::VM& vm = init.vm;
|
||||
@@ -3525,6 +3555,9 @@ void GlobalObject::addBuiltinGlobals(JSC::VM& vm)
|
||||
extern "C" void Crypto__randomUUID__put(JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue value);
|
||||
extern "C" void Crypto__getRandomValues__put(JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue value);
|
||||
|
||||
DEFINE_BUN_LAZY_GETTER(BUN_LAZY_GETTER_FN_NAME(password), passwordObject)
|
||||
DEFINE_BUN_LAZY_GETTER(BUN_LAZY_GETTER_FN_NAME(passwordSync), passwordSyncObject)
|
||||
|
||||
// This is not a publicly exposed API currently.
|
||||
// This is used by the bundler to make Response, Request, FetchEvent,
|
||||
// and any other objects available globally.
|
||||
@@ -3583,6 +3616,19 @@ void GlobalObject::installAPIGlobals(JSClassRef* globals, int count, JSC::VM& vm
|
||||
JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0);
|
||||
}
|
||||
|
||||
// TODO: code generate these
|
||||
{
|
||||
JSC::Identifier identifier = JSC::Identifier::fromString(vm, "password"_s);
|
||||
object->putDirectCustomAccessor(vm, identifier, JSC::CustomGetterSetter::create(vm, BUN_LAZY_GETTER_FN_NAME(password), nullptr),
|
||||
JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0);
|
||||
}
|
||||
|
||||
{
|
||||
JSC::Identifier identifier = JSC::Identifier::fromString(vm, "passwordSync"_s);
|
||||
object->putDirectCustomAccessor(vm, identifier, JSC::CustomGetterSetter::create(vm, BUN_LAZY_GETTER_FN_NAME(passwordSync), nullptr),
|
||||
JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0);
|
||||
}
|
||||
|
||||
{
|
||||
JSC::Identifier identifier = JSC::Identifier::fromString(vm, "readableStreamToArrayBuffer"_s);
|
||||
object->putDirectBuiltinFunction(vm, this, identifier, readableStreamReadableStreamToArrayBufferCodeGenerator(vm),
|
||||
@@ -3853,6 +3899,8 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
|
||||
thisObject->m_lazyTestModuleObject.visit(visitor);
|
||||
thisObject->m_lazyPreloadTestModuleObject.visit(visitor);
|
||||
thisObject->m_commonJSModuleObjectStructure.visit(visitor);
|
||||
thisObject->m_lazyPasswordObject.visit(visitor);
|
||||
thisObject->m_lazyPasswordSyncObject.visit(visitor);
|
||||
thisObject->m_commonJSFunctionArgumentsStructure.visit(visitor);
|
||||
thisObject->m_cachedGlobalObjectStructure.visit(visitor);
|
||||
thisObject->m_cachedGlobalProxyStructure.visit(visitor);
|
||||
@@ -4094,7 +4142,6 @@ JSC::JSObject* GlobalObject::moduleLoaderCreateImportMetaProperties(JSGlobalObje
|
||||
JSModuleRecord* record,
|
||||
JSValue val)
|
||||
{
|
||||
|
||||
JSC::VM& vm = globalObject->vm();
|
||||
JSC::JSString* keyString = key.toStringOrNull(globalObject);
|
||||
if (UNLIKELY(!keyString))
|
||||
@@ -4108,7 +4155,6 @@ JSC::JSValue GlobalObject::moduleLoaderEvaluate(JSGlobalObject* globalObject,
|
||||
JSValue moduleRecordValue, JSValue scriptFetcher,
|
||||
JSValue sentValue, JSValue resumeMode)
|
||||
{
|
||||
|
||||
if (UNLIKELY(scriptFetcher && scriptFetcher.isObject())) {
|
||||
return scriptFetcher;
|
||||
}
|
||||
|
||||
@@ -264,6 +264,9 @@ public:
|
||||
|
||||
Structure* commonJSFunctionArgumentsStructure() { return m_commonJSFunctionArgumentsStructure.getInitializedOnMainThread(this); }
|
||||
|
||||
JSObject* passwordSyncObject() { return m_lazyPasswordSyncObject.getInitializedOnMainThread(this); }
|
||||
JSObject* passwordObject() { return m_lazyPasswordObject.getInitializedOnMainThread(this); }
|
||||
|
||||
JSWeakMap* vmModuleContextMap() { return m_vmModuleContextMap.getInitializedOnMainThread(this); }
|
||||
|
||||
JSC::JSObject* processObject()
|
||||
@@ -476,6 +479,8 @@ private:
|
||||
LazyProperty<JSGlobalObject, JSObject> m_lazyRequireCacheObject;
|
||||
LazyProperty<JSGlobalObject, JSObject> m_lazyTestModuleObject;
|
||||
LazyProperty<JSGlobalObject, JSObject> m_lazyPreloadTestModuleObject;
|
||||
LazyProperty<JSGlobalObject, JSObject> m_lazyPasswordSyncObject;
|
||||
LazyProperty<JSGlobalObject, JSObject> m_lazyPasswordObject;
|
||||
|
||||
LazyProperty<JSGlobalObject, JSFunction> m_bunSleepThenCallback;
|
||||
LazyProperty<JSGlobalObject, Structure> m_cachedGlobalObjectStructure;
|
||||
|
||||
@@ -224,8 +224,6 @@ static inline JSC::EncodedJSValue addListener(JSC::JSGlobalObject* lexicalGlobal
|
||||
auto& vm = JSC::getVM(lexicalGlobalObject);
|
||||
auto throwScope = DECLARE_THROW_SCOPE(vm);
|
||||
JSC::JSValue actualThis = callFrame->thisValue();
|
||||
UNUSED_PARAM(throwScope);
|
||||
UNUSED_PARAM(callFrame);
|
||||
auto& impl = castedThis->wrapped();
|
||||
if (UNLIKELY(callFrame->argumentCount() < 2))
|
||||
return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));
|
||||
@@ -330,8 +328,6 @@ static inline JSC::EncodedJSValue jsEventEmitterPrototypeFunction_removeListener
|
||||
auto& vm = JSC::getVM(lexicalGlobalObject);
|
||||
auto throwScope = DECLARE_THROW_SCOPE(vm);
|
||||
JSC::JSValue actualThis = callFrame->thisValue();
|
||||
UNUSED_PARAM(throwScope);
|
||||
UNUSED_PARAM(callFrame);
|
||||
auto& impl = castedThis->wrapped();
|
||||
if (UNLIKELY(callFrame->argumentCount() < 1))
|
||||
return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));
|
||||
@@ -362,12 +358,11 @@ static inline JSC::EncodedJSValue jsEventEmitterPrototypeFunction_removeAllListe
|
||||
{
|
||||
auto& vm = JSC::getVM(lexicalGlobalObject);
|
||||
auto throwScope = DECLARE_THROW_SCOPE(vm);
|
||||
UNUSED_PARAM(throwScope);
|
||||
UNUSED_PARAM(callFrame);
|
||||
JSValue actualThis = callFrame->thisValue();
|
||||
auto& impl = castedThis->wrapped();
|
||||
if (callFrame->argumentCount() == 0) {
|
||||
impl.removeAllListeners();
|
||||
return JSValue::encode(jsUndefined());
|
||||
RELEASE_AND_RETURN(throwScope, JSValue::encode(actualThis));
|
||||
}
|
||||
|
||||
EnsureStillAliveScope argument0 = callFrame->uncheckedArgument(0);
|
||||
@@ -375,7 +370,8 @@ static inline JSC::EncodedJSValue jsEventEmitterPrototypeFunction_removeAllListe
|
||||
RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
|
||||
auto result = JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.removeAllListenersForBindings(WTFMove(eventType)); }));
|
||||
RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
|
||||
return result;
|
||||
impl.setThisObject(actualThis);
|
||||
RELEASE_AND_RETURN(throwScope, JSValue::encode(actualThis));
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsEventEmitterPrototypeFunction_removeAllListeners, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
|
||||
@@ -387,8 +383,6 @@ static inline JSC::EncodedJSValue jsEventEmitterPrototypeFunction_emitBody(JSC::
|
||||
{
|
||||
auto& vm = JSC::getVM(lexicalGlobalObject);
|
||||
auto throwScope = DECLARE_THROW_SCOPE(vm);
|
||||
UNUSED_PARAM(throwScope);
|
||||
UNUSED_PARAM(callFrame);
|
||||
auto& impl = castedThis->wrapped();
|
||||
size_t argumentCount = callFrame->argumentCount();
|
||||
if (UNLIKELY(argumentCount < 1))
|
||||
@@ -411,7 +405,6 @@ static inline JSC::EncodedJSValue jsEventEmitterPrototypeFunction_eventNamesBody
|
||||
{
|
||||
auto& vm = JSC::getVM(lexicalGlobalObject);
|
||||
auto throwScope = DECLARE_THROW_SCOPE(vm);
|
||||
UNUSED_PARAM(throwScope);
|
||||
UNUSED_PARAM(callFrame);
|
||||
auto& impl = castedThis->wrapped();
|
||||
JSC::MarkedArgumentBuffer args;
|
||||
@@ -430,8 +423,6 @@ static inline JSC::EncodedJSValue jsEventEmitterPrototypeFunction_listenerCountB
|
||||
{
|
||||
auto& vm = JSC::getVM(lexicalGlobalObject);
|
||||
auto throwScope = DECLARE_THROW_SCOPE(vm);
|
||||
UNUSED_PARAM(throwScope);
|
||||
UNUSED_PARAM(callFrame);
|
||||
auto& impl = castedThis->wrapped();
|
||||
if (UNLIKELY(callFrame->argumentCount() < 1))
|
||||
return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));
|
||||
@@ -449,8 +440,6 @@ static inline JSC::EncodedJSValue jsEventEmitterPrototypeFunction_listenersBody(
|
||||
{
|
||||
auto& vm = JSC::getVM(lexicalGlobalObject);
|
||||
auto throwScope = DECLARE_THROW_SCOPE(vm);
|
||||
UNUSED_PARAM(throwScope);
|
||||
UNUSED_PARAM(callFrame);
|
||||
auto& impl = castedThis->wrapped();
|
||||
if (UNLIKELY(callFrame->argumentCount() < 1))
|
||||
return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));
|
||||
@@ -534,8 +523,6 @@ JSC_DEFINE_HOST_FUNCTION(Events_functionGetEventListeners,
|
||||
{
|
||||
auto& vm = JSC::getVM(lexicalGlobalObject);
|
||||
auto throwScope = DECLARE_THROW_SCOPE(vm);
|
||||
UNUSED_PARAM(throwScope);
|
||||
UNUSED_PARAM(callFrame);
|
||||
if (UNLIKELY(callFrame->argumentCount() < 2))
|
||||
return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));
|
||||
auto argument0 = jsEventEmitterCast(vm, lexicalGlobalObject, callFrame->uncheckedArgument(0));
|
||||
@@ -558,8 +545,6 @@ JSC_DEFINE_HOST_FUNCTION(Events_functionListenerCount,
|
||||
{
|
||||
auto& vm = JSC::getVM(lexicalGlobalObject);
|
||||
auto throwScope = DECLARE_THROW_SCOPE(vm);
|
||||
UNUSED_PARAM(throwScope);
|
||||
UNUSED_PARAM(callFrame);
|
||||
if (UNLIKELY(callFrame->argumentCount() < 2))
|
||||
return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));
|
||||
auto argument0 = jsEventEmitterCast(vm, lexicalGlobalObject, callFrame->uncheckedArgument(0));
|
||||
@@ -578,8 +563,6 @@ JSC_DEFINE_HOST_FUNCTION(Events_functionOnce,
|
||||
{
|
||||
auto& vm = JSC::getVM(lexicalGlobalObject);
|
||||
auto throwScope = DECLARE_THROW_SCOPE(vm);
|
||||
UNUSED_PARAM(throwScope);
|
||||
UNUSED_PARAM(callFrame);
|
||||
|
||||
if (UNLIKELY(callFrame->argumentCount() < 3))
|
||||
return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));
|
||||
@@ -604,8 +587,6 @@ JSC_DEFINE_HOST_FUNCTION(Events_functionOn,
|
||||
{
|
||||
auto& vm = JSC::getVM(lexicalGlobalObject);
|
||||
auto throwScope = DECLARE_THROW_SCOPE(vm);
|
||||
UNUSED_PARAM(throwScope);
|
||||
UNUSED_PARAM(callFrame);
|
||||
|
||||
if (UNLIKELY(callFrame->argumentCount() < 3))
|
||||
return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));
|
||||
|
||||
@@ -51,6 +51,10 @@ JSC::JSArrayBuffer* WebCoreTypedArrayController::toJS(JSC::JSGlobalObject* lexic
|
||||
|
||||
void WebCoreTypedArrayController::registerWrapper(JSC::JSGlobalObject* globalObject, JSC::ArrayBuffer* native, JSC::JSArrayBuffer* wrapper)
|
||||
{
|
||||
// require("vm") can be used to create an ArrayBuffer
|
||||
if (UNLIKELY(!globalObject->inherits<JSDOMGlobalObject>()))
|
||||
return;
|
||||
|
||||
cacheWrapper(JSC::jsCast<JSDOMGlobalObject*>(globalObject)->world(), native, wrapper);
|
||||
}
|
||||
|
||||
|
||||
@@ -312,6 +312,18 @@ pub const SliceOrBuffer = union(Tag) {
|
||||
string: JSC.ZigString.Slice,
|
||||
buffer: Buffer,
|
||||
|
||||
pub fn ensureCloned(this: *SliceOrBuffer, allocator: std.mem.Allocator) !void {
|
||||
if (this.* == .string) {
|
||||
this.string = try this.string.cloneIfNeeded(allocator);
|
||||
return;
|
||||
}
|
||||
|
||||
const bytes = this.buffer.buffer.byteSlice();
|
||||
this.* = .{
|
||||
.string = JSC.ZigString.Slice.from(try allocator.dupe(u8, bytes), allocator),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(this: SliceOrBuffer) void {
|
||||
switch (this) {
|
||||
.string => {
|
||||
|
||||
@@ -25,7 +25,7 @@ pub const huge_allocator_threshold: comptime_int = @import("./memory_allocator.z
|
||||
pub const fs_allocator = default_allocator;
|
||||
|
||||
pub const C = @import("c.zig");
|
||||
|
||||
pub const sha = @import("./sha.zig");
|
||||
pub const FeatureFlags = @import("feature_flags.zig");
|
||||
pub const meta = @import("./meta.zig");
|
||||
pub const ComptimeStringMap = @import("./comptime_string_map.zig").ComptimeStringMap;
|
||||
|
||||
@@ -189,11 +189,19 @@ export function runSetupFunction(this: BundlerPlugin, setup: Setup, config: Buil
|
||||
return processSetupResult();
|
||||
}
|
||||
|
||||
export function runOnResolvePlugins(this: BundlerPlugin, specifier, inputNamespace, importer, internalID, kindId) {
|
||||
export function runOnResolvePlugins(
|
||||
this: BundlerPlugin,
|
||||
specifier,
|
||||
inputNamespace,
|
||||
importer,
|
||||
internalID,
|
||||
kindId,
|
||||
resolveDir,
|
||||
) {
|
||||
// Must be kept in sync with ImportRecord.label
|
||||
const kind = $ImportKindIdToLabel[kindId];
|
||||
|
||||
var promiseResult: any = (async (inputPath, inputNamespace, importer, kind) => {
|
||||
var promiseResult: any = (async (inputPath, inputNamespace, importer, kind, resolveDir) => {
|
||||
var { onResolve, onLoad } = this;
|
||||
var results = onResolve.$get(inputNamespace);
|
||||
if (!results) {
|
||||
@@ -207,7 +215,7 @@ export function runOnResolvePlugins(this: BundlerPlugin, specifier, inputNamespa
|
||||
path: inputPath,
|
||||
importer,
|
||||
namespace: inputNamespace,
|
||||
// resolveDir
|
||||
resolveDir,
|
||||
kind,
|
||||
// pluginData
|
||||
});
|
||||
@@ -229,8 +237,12 @@ export function runOnResolvePlugins(this: BundlerPlugin, specifier, inputNamespa
|
||||
}
|
||||
|
||||
var { path, namespace: userNamespace = inputNamespace, external } = result;
|
||||
if (!(typeof path === "string") || !(typeof userNamespace === "string")) {
|
||||
throw new TypeError("onResolve plugins must return an object with a string 'path' and string 'loader' field");
|
||||
if (!(typeof path === "string")) {
|
||||
throw new TypeError("onResolve: expected 'path' to be a string");
|
||||
}
|
||||
|
||||
if (!(typeof userNamespace === "string")) {
|
||||
throw new TypeError("onResolve: expected 'namespace' to be a string");
|
||||
}
|
||||
|
||||
if (!path) {
|
||||
@@ -241,7 +253,7 @@ export function runOnResolvePlugins(this: BundlerPlugin, specifier, inputNamespa
|
||||
userNamespace = inputNamespace;
|
||||
}
|
||||
if (typeof external !== "boolean" && !$isUndefinedOrNull(external)) {
|
||||
throw new TypeError('onResolve plugins "external" field must be boolean or unspecified');
|
||||
throw new TypeError("onResolve: expected 'external' to be boolean");
|
||||
}
|
||||
|
||||
if (!external) {
|
||||
@@ -271,7 +283,7 @@ export function runOnResolvePlugins(this: BundlerPlugin, specifier, inputNamespa
|
||||
|
||||
this.onResolveAsync(internalID, null, null, null);
|
||||
return null;
|
||||
})(specifier, inputNamespace, importer, kind);
|
||||
})(specifier, inputNamespace, importer, kind, resolveDir);
|
||||
|
||||
while (
|
||||
promiseResult &&
|
||||
@@ -329,6 +341,9 @@ export function runOnLoadPlugins(this: BundlerPlugin, internalID, path, namespac
|
||||
}
|
||||
|
||||
var { contents, loader = defaultLoader } = result as OnLoadResultSourceCode & OnLoadResultObject;
|
||||
|
||||
// TODO: Support "object" loader
|
||||
|
||||
if (!(typeof contents === "string") && !$isTypedArrayView(contents)) {
|
||||
throw new TypeError('onLoad plugins must return an object with "contents" as a string or Uint8Array');
|
||||
}
|
||||
|
||||
6
src/js/out/WebCoreJSBuiltins.cpp
generated
6
src/js/out/WebCoreJSBuiltins.cpp
generated
@@ -22,9 +22,9 @@ const char* const s_bundlerPluginRunSetupFunctionCode = "(function (_,E){\"use s
|
||||
const JSC::ConstructAbility s_bundlerPluginRunOnResolvePluginsCodeConstructAbility = JSC::ConstructAbility::CannotConstruct;
|
||||
const JSC::ConstructorKind s_bundlerPluginRunOnResolvePluginsCodeConstructorKind = JSC::ConstructorKind::None;
|
||||
const JSC::ImplementationVisibility s_bundlerPluginRunOnResolvePluginsCodeImplementationVisibility = JSC::ImplementationVisibility::Public;
|
||||
const int s_bundlerPluginRunOnResolvePluginsCodeLength = 1711;
|
||||
const int s_bundlerPluginRunOnResolvePluginsCodeLength = 1732;
|
||||
static const JSC::Intrinsic s_bundlerPluginRunOnResolvePluginsCodeIntrinsic = JSC::NoIntrinsic;
|
||||
const char* const s_bundlerPluginRunOnResolvePluginsCode = "(function (_,w,g,y,b){\"use strict\";const j=[\"entry-point\",\"import-statement\",\"require-call\",\"dynamic-import\",\"require-resolve\",\"import-rule\",\"url-token\",\"internal\"][b];var q=(async(z,A,B,C)=>{var{onResolve:E,onLoad:F}=this,G=E.@get(A);if(!G)return this.onResolveAsync(y,null,null,null),null;for(let[O,Q]of G)if(O.test(z)){var H=Q({path:z,importer:B,namespace:A,kind:C});while(H&&@isPromise(H)&&(@getPromiseInternalField(H,@promiseFieldFlags)&@promiseStateMask)===@promiseStateFulfilled)H=@getPromiseInternalField(H,@promiseFieldReactionsOrResult);if(H&&@isPromise(H))H=await H;if(!H||!@isObject(H))continue;var{path:J,namespace:K=A,external:M}=H;if(typeof J!==\"string\"||typeof K!==\"string\")@throwTypeError(\"onResolve plugins must return an object with a string 'path' and string 'loader' field\");if(!J)continue;if(!K)K=A;if(typeof M!==\"boolean\"&&!@isUndefinedOrNull(M))@throwTypeError('onResolve plugins \"external\" field must be boolean or unspecified');if(!M){if(K===\"file\"){if(darwin!==\"win32\"){if(J[0]!==\"/\"||J.includes(\"..\"))@throwTypeError('onResolve plugin \"path\" must be absolute when the namespace is \"file\"')}}if(K===\"dataurl\"){if(!J.startsWith(\"data:\"))@throwTypeError('onResolve plugin \"path\" must start with \"data:\" when the namespace is \"dataurl\"')}if(K&&K!==\"file\"&&(!F||!F.@has(K)))@throwTypeError(`Expected onLoad plugin for namespace ${K} to exist`)}return this.onResolveAsync(y,J,K,M),null}return this.onResolveAsync(y,null,null,null),null})(_,w,g,j);while(q&&@isPromise(q)&&(@getPromiseInternalField(q,@promiseFieldFlags)&@promiseStateMask)===@promiseStateFulfilled)q=@getPromiseInternalField(q,@promiseFieldReactionsOrResult);if(q&&@isPromise(q))q.then(()=>{},(z)=>{this.addError(y,z,0)})})\n";
|
||||
const char* const s_bundlerPluginRunOnResolvePluginsCode = "(function (_,g,y,F,j,q){\"use strict\";const w=[\"entry-point\",\"import-statement\",\"require-call\",\"dynamic-import\",\"require-resolve\",\"import-rule\",\"url-token\",\"internal\"][j];var z=(async(A,B,C,E,G)=>{var{onResolve:H,onLoad:J}=this,K=H.@get(B);if(!K)return this.onResolveAsync(F,null,null,null),null;for(let[T,U]of K)if(T.test(A)){var M=U({path:A,importer:C,namespace:B,resolveDir:G,kind:E});while(M&&@isPromise(M)&&(@getPromiseInternalField(M,@promiseFieldFlags)&@promiseStateMask)===@promiseStateFulfilled)M=@getPromiseInternalField(M,@promiseFieldReactionsOrResult);if(M&&@isPromise(M))M=await M;if(!M||!@isObject(M))continue;var{path:O,namespace:Q=B,external:S}=M;if(typeof O!==\"string\")@throwTypeError(\"onResolve: expected 'path' to be a string\");if(typeof Q!==\"string\")@throwTypeError(\"onResolve: expected 'namespace' to be a string\");if(!O)continue;if(!Q)Q=B;if(typeof S!==\"boolean\"&&!@isUndefinedOrNull(S))@throwTypeError(\"onResolve: expected 'external' to be boolean\");if(!S){if(Q===\"file\"){if(darwin!==\"win32\"){if(O[0]!==\"/\"||O.includes(\"..\"))@throwTypeError('onResolve plugin \"path\" must be absolute when the namespace is \"file\"')}}if(Q===\"dataurl\"){if(!O.startsWith(\"data:\"))@throwTypeError('onResolve plugin \"path\" must start with \"data:\" when the namespace is \"dataurl\"')}if(Q&&Q!==\"file\"&&(!J||!J.@has(Q)))@throwTypeError(`Expected onLoad plugin for namespace ${Q} to exist`)}return this.onResolveAsync(F,O,Q,S),null}return this.onResolveAsync(F,null,null,null),null})(_,g,y,w,q);while(z&&@isPromise(z)&&(@getPromiseInternalField(z,@promiseFieldFlags)&@promiseStateMask)===@promiseStateFulfilled)z=@getPromiseInternalField(z,@promiseFieldReactionsOrResult);if(z&&@isPromise(z))z.then(()=>{},(A)=>{this.addError(F,A,0)})})\n";
|
||||
|
||||
// runOnLoadPlugins
|
||||
const JSC::ConstructAbility s_bundlerPluginRunOnLoadPluginsCodeConstructAbility = JSC::ConstructAbility::CannotConstruct;
|
||||
@@ -32,7 +32,7 @@ const JSC::ConstructorKind s_bundlerPluginRunOnLoadPluginsCodeConstructorKind =
|
||||
const JSC::ImplementationVisibility s_bundlerPluginRunOnLoadPluginsCodeImplementationVisibility = JSC::ImplementationVisibility::Public;
|
||||
const int s_bundlerPluginRunOnLoadPluginsCodeLength = 1325;
|
||||
static const JSC::Intrinsic s_bundlerPluginRunOnLoadPluginsCodeIntrinsic = JSC::NoIntrinsic;
|
||||
const char* const s_bundlerPluginRunOnLoadPluginsCode = "(function (b,g,j,q){\"use strict\";const v={jsx:0,js:1,ts:2,tsx:3,css:4,file:5,json:6,toml:7,wasm:8,napi:9,base64:10,dataurl:11,text:12},w=[\"jsx\",\"js\",\"ts\",\"tsx\",\"css\",\"file\",\"json\",\"toml\",\"wasm\",\"napi\",\"base64\",\"dataurl\",\"text\"][q];var x=(async(y,z,B,C)=>{var F=this.onLoad.@get(B);if(!F)return this.onLoadAsync(y,null,null),null;for(let[K,Q]of F)if(K.test(z)){var G=Q({path:z,namespace:B,loader:C});while(G&&@isPromise(G)&&(@getPromiseInternalField(G,@promiseFieldFlags)&@promiseStateMask)===@promiseStateFulfilled)G=@getPromiseInternalField(G,@promiseFieldReactionsOrResult);if(G&&@isPromise(G))G=await G;if(!G||!@isObject(G))continue;var{contents:H,loader:J=C}=G;if(typeof H!==\"string\"&&!@isTypedArrayView(H))@throwTypeError('onLoad plugins must return an object with \"contents\" as a string or Uint8Array');if(typeof J!==\"string\")@throwTypeError('onLoad plugins must return an object with \"loader\" as a string');const T=v[J];if(T===@undefined)@throwTypeError(`Loader ${J} is not supported.`);return this.onLoadAsync(y,H,T),null}return this.onLoadAsync(y,null,null),null})(b,g,j,w);while(x&&@isPromise(x)&&(@getPromiseInternalField(x,@promiseFieldFlags)&@promiseStateMask)===@promiseStateFulfilled)x=@getPromiseInternalField(x,@promiseFieldReactionsOrResult);if(x&&@isPromise(x))x.then(()=>{},(y)=>{this.addError(b,y,1)})})\n";
|
||||
const char* const s_bundlerPluginRunOnLoadPluginsCode = "(function (w,y,_,g){\"use strict\";const j={jsx:0,js:1,ts:2,tsx:3,css:4,file:5,json:6,toml:7,wasm:8,napi:9,base64:10,dataurl:11,text:12},q=[\"jsx\",\"js\",\"ts\",\"tsx\",\"css\",\"file\",\"json\",\"toml\",\"wasm\",\"napi\",\"base64\",\"dataurl\",\"text\"][g];var v=(async(x,z,B,C)=>{var F=this.onLoad.@get(B);if(!F)return this.onLoadAsync(x,null,null),null;for(let[K,P]of F)if(K.test(z)){var G=P({path:z,namespace:B,loader:C});while(G&&@isPromise(G)&&(@getPromiseInternalField(G,@promiseFieldFlags)&@promiseStateMask)===@promiseStateFulfilled)G=@getPromiseInternalField(G,@promiseFieldReactionsOrResult);if(G&&@isPromise(G))G=await G;if(!G||!@isObject(G))continue;var{contents:H,loader:J=C}=G;if(typeof H!==\"string\"&&!@isTypedArrayView(H))@throwTypeError('onLoad plugins must return an object with \"contents\" as a string or Uint8Array');if(typeof J!==\"string\")@throwTypeError('onLoad plugins must return an object with \"loader\" as a string');const Q=j[J];if(Q===@undefined)@throwTypeError(`Loader ${J} is not supported.`);return this.onLoadAsync(x,H,Q),null}return this.onLoadAsync(x,null,null),null})(w,y,_,q);while(v&&@isPromise(v)&&(@getPromiseInternalField(v,@promiseFieldFlags)&@promiseStateMask)===@promiseStateFulfilled)v=@getPromiseInternalField(v,@promiseFieldReactionsOrResult);if(v&&@isPromise(v))v.then(()=>{},(x)=>{this.addError(w,x,1)})})\n";
|
||||
|
||||
#define DEFINE_BUILTIN_GENERATOR(codeName, functionName, overriddenName, argumentCount) \
|
||||
JSC::FunctionExecutable* codeName##Generator(JSC::VM& vm) \
|
||||
|
||||
2
src/js/out/WebCoreJSBuiltins.h
generated
2
src/js/out/WebCoreJSBuiltins.h
generated
@@ -42,7 +42,7 @@ extern const JSC::ImplementationVisibility s_bundlerPluginRunOnLoadPluginsCodeIm
|
||||
|
||||
#define WEBCORE_FOREACH_BUNDLERPLUGIN_BUILTIN_DATA(macro) \
|
||||
macro(runSetupFunction, bundlerPluginRunSetupFunction, 2) \
|
||||
macro(runOnResolvePlugins, bundlerPluginRunOnResolvePlugins, 5) \
|
||||
macro(runOnResolvePlugins, bundlerPluginRunOnResolvePlugins, 7) \
|
||||
macro(runOnLoadPlugins, bundlerPluginRunOnLoadPlugins, 4) \
|
||||
|
||||
#define WEBCORE_FOREACH_BUNDLERPLUGIN_BUILTIN_CODE(macro) \
|
||||
|
||||
@@ -2070,13 +2070,14 @@ fn NewLexer_(
|
||||
lexer.step();
|
||||
|
||||
var has_set_flags_start = false;
|
||||
const min_flag = comptime std.mem.min(u8, "dgimsuy");
|
||||
const max_flag = comptime std.mem.max(u8, "dgimsuy");
|
||||
const flag_characters = "dgimsuvy";
|
||||
const min_flag = comptime std.mem.min(u8, flag_characters);
|
||||
const max_flag = comptime std.mem.max(u8, flag_characters);
|
||||
const RegexpFlags = std.bit_set.IntegerBitSet((max_flag - min_flag) + 1);
|
||||
var flags = RegexpFlags.initEmpty();
|
||||
while (isIdentifierContinue(lexer.code_point)) {
|
||||
switch (lexer.code_point) {
|
||||
'd', 'g', 'i', 'm', 's', 'u', 'y' => {
|
||||
'd', 'g', 'i', 'm', 's', 'u', 'y', 'v' => {
|
||||
if (!has_set_flags_start) {
|
||||
lexer.regex_flags_start = @truncate(u16, lexer.end - lexer.start);
|
||||
has_set_flags_start = true;
|
||||
@@ -2640,7 +2641,7 @@ fn NewLexer_(
|
||||
// them. <CR><LF> and <CR> LineTerminatorSequences are normalized to
|
||||
// <LF> for both TV and TRV. An explicit EscapeSequence is needed to
|
||||
// include a <CR> or <CR><LF> sequence.
|
||||
var bytes = MutableString.init(lexer.allocator, text.len) catch unreachable;
|
||||
var bytes = MutableString.initCopy(lexer.allocator, text) catch @panic("Out of memory");
|
||||
var end: usize = 0;
|
||||
var i: usize = 0;
|
||||
var c: u8 = '0';
|
||||
@@ -2662,7 +2663,7 @@ fn NewLexer_(
|
||||
end += 1;
|
||||
}
|
||||
|
||||
return bytes.toOwnedSliceLength(end + 1);
|
||||
return bytes.toOwnedSliceLength(end);
|
||||
}
|
||||
|
||||
fn parseNumericLiteralOrDot(lexer: *LexerType) !void {
|
||||
|
||||
BIN
test/bun.lockb
BIN
test/bun.lockb
Binary file not shown.
@@ -894,4 +894,42 @@ describe("bundler", () => {
|
||||
stdout: "it worked\nit worked\nit worked\nit worked",
|
||||
},
|
||||
});
|
||||
itBundled("edgecase/IsBuffer1", {
|
||||
files: {
|
||||
"/entry.js": /* js */ `
|
||||
import isBuffer from 'lodash-es/isBuffer';
|
||||
if(isBuffer !== 1) throw 'fail';
|
||||
console.log('pass');
|
||||
`,
|
||||
"/node_modules/lodash-es/isBuffer.js": /* js */ `
|
||||
var freeExports = typeof exports == 'object';
|
||||
// this is using the 'freeExports' variable but giving a predictable outcome
|
||||
const isBuffer = freeExports ? 1 : 1;
|
||||
export default isBuffer;
|
||||
`,
|
||||
},
|
||||
run: {
|
||||
stdout: "pass",
|
||||
},
|
||||
});
|
||||
itBundled("edgecase/IsBuffer2", {
|
||||
files: {
|
||||
"/entry.js": /* js */ `
|
||||
import isBuffer from 'lodash-es/isBuffer';
|
||||
if(isBuffer !== 1) throw 'fail';
|
||||
console.log('pass');
|
||||
`,
|
||||
"/node_modules/lodash-es/isBuffer.js": /* js */ `
|
||||
var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports;
|
||||
var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module;
|
||||
|
||||
// this is using the 'freeExports' variable but giving a predictable outcome
|
||||
const isBuffer = [freeExports, freeModule] ? 1 : 1;
|
||||
export default isBuffer;
|
||||
`,
|
||||
},
|
||||
run: {
|
||||
stdout: "pass",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -43,4 +43,29 @@ describe("bundler", () => {
|
||||
stdout: "<!DOCTYPE html><html><head></head><body><h1>Hello World</h1><p>This is an example.</p></body></html>",
|
||||
},
|
||||
});
|
||||
itBundled("npm/LodashES", {
|
||||
install: ["lodash-es"],
|
||||
files: {
|
||||
"/entry.ts": /* tsx */ `
|
||||
import { isEqual, isBuffer } from "lodash-es";
|
||||
|
||||
// https://github.com/oven-sh/bun/issues/3206
|
||||
if(!isEqual({a: 1}, {a: 1})) throw "error 1";
|
||||
if(isEqual({a: 1}, {a: 2})) throw "error 2";
|
||||
|
||||
// Uncomment when https://github.com/lodash/lodash/issues/5660 is fixed
|
||||
// It prevents isBuffer from working at all since it evaluates to 'stubFalse'
|
||||
// if(!isBuffer(Buffer.from("hello"))) throw "error 3";
|
||||
// if(isBuffer("hello")) throw "error 4";
|
||||
// if(isBuffer({})) throw "error 5";
|
||||
// if(isBuffer(new Uint8Array([1]))) throw "error 6";
|
||||
// if(isBuffer(new ArrayBuffer(1))) throw "error 7";
|
||||
|
||||
console.log('pass')
|
||||
`,
|
||||
},
|
||||
run: {
|
||||
stdout: "pass",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -830,4 +830,32 @@ describe("bundler", () => {
|
||||
},
|
||||
};
|
||||
});
|
||||
itBundled("plugin/resolveDir", ({ getConfigRef, root }) => {
|
||||
return {
|
||||
run: true,
|
||||
files: {
|
||||
"index.ts": /* ts */ `
|
||||
import "./foo.magic";
|
||||
`,
|
||||
},
|
||||
entryPoints: ["./index.ts"],
|
||||
plugins(build) {
|
||||
build.onResolve({ "filter": /.magic$/ }, args => {
|
||||
console.log({ root, resolveDir: args.resolveDir });
|
||||
expect(args.resolveDir).toBeDefined();
|
||||
expect(args.resolveDir).toEqual(root);
|
||||
return {
|
||||
path: "magic",
|
||||
"namespace": "magic",
|
||||
};
|
||||
});
|
||||
build.onLoad({ namespace: "magic", "filter": /.*/ }, _args => {
|
||||
return {
|
||||
contents: "",
|
||||
loader: "ts",
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "fs";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import dedent from "dedent";
|
||||
import { bunEnv, bunExe } from "harness";
|
||||
import { tmpdir } from "os";
|
||||
@@ -42,7 +43,7 @@ export const RUN_UNCHECKED_TESTS = false;
|
||||
|
||||
const outBaseTemplate = path.join(tmpdir(), "bun-build-tests", `${ESBUILD ? "esbuild" : "bun"}-`);
|
||||
if (!existsSync(path.dirname(outBaseTemplate))) mkdirSync(path.dirname(outBaseTemplate), { recursive: true });
|
||||
const outBase = mkdtempSync(outBaseTemplate);
|
||||
const outBase = fs.realpathSync(mkdtempSync(outBaseTemplate));
|
||||
const testsRan = new Set();
|
||||
|
||||
if (ESBUILD) {
|
||||
@@ -410,7 +411,7 @@ function expectBundled(
|
||||
backend = plugins !== undefined ? "api" : "cli";
|
||||
}
|
||||
|
||||
const root = path.join(outBase, id.replaceAll("/", path.sep));
|
||||
const root = await path.join(outBase, id.replaceAll("/", path.sep));
|
||||
if (DEBUG) console.log("root:", root);
|
||||
|
||||
const entryPaths = entryPoints.map(file => path.join(root, file));
|
||||
@@ -1186,6 +1187,9 @@ for (const [key, blob] of build.outputs) {
|
||||
},
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
if (DEBUG) {
|
||||
console.log({ stdout: stdout!.toString("utf-8"), stderr: stderr!.toString("utf-8") });
|
||||
}
|
||||
|
||||
if (run.error) {
|
||||
if (success) {
|
||||
@@ -1287,6 +1291,7 @@ export function itBundled(
|
||||
): BundlerTestRef {
|
||||
if (typeof opts === "function") {
|
||||
const fn = opts;
|
||||
// console.log("outbase", outBase);
|
||||
opts = opts({ root: path.join(outBase, id.replaceAll("/", path.sep)), getConfigRef });
|
||||
// @ts-expect-error
|
||||
opts._referenceFn = fn;
|
||||
|
||||
@@ -30,7 +30,7 @@ fi
|
||||
export FORCE_COLOR=1
|
||||
$BUN test bundler_ esbuild/ 2>&1 \
|
||||
| perl -ne 'print unless /^\e\[0m$/' \
|
||||
| grep -v -P '\x1b\[0m\x1b\[33m-\x1b\[2m \x1b\[0m\x1b\[2mbundler' --text \
|
||||
| grep -v '\x1b\[0m\x1b\[33m-\x1b\[2m \x1b\[0m\x1b\[2mbundler' --text \
|
||||
| grep -v ".test.ts:$" --text \
|
||||
| tee /tmp/run-single-bundler-test.txt \
|
||||
| grep "root:" -v --text
|
||||
|
||||
279
test/js/bun/util/password.test.ts
Normal file
279
test/js/bun/util/password.test.ts
Normal file
@@ -0,0 +1,279 @@
|
||||
import { test, expect, describe } from "bun:test";
|
||||
|
||||
import { Password, password, passwordSync } from "bun";
|
||||
|
||||
const placeholder = "hey";
|
||||
|
||||
describe("hash", () => {
|
||||
describe("arguments parsing", () => {
|
||||
for (let { hash } of [password, passwordSync]) {
|
||||
test("no blank password allowed", () => {
|
||||
expect(() => hash("")).toThrow("password must not be empty");
|
||||
});
|
||||
|
||||
test("password is required", () => {
|
||||
// @ts-expect-error
|
||||
expect(() => hash()).toThrow();
|
||||
});
|
||||
|
||||
test("invalid algorithm throws", () => {
|
||||
// @ts-expect-error
|
||||
expect(() => hash(placeholder, "scrpyt")).toThrow();
|
||||
// @ts-expect-error
|
||||
expect(() => hash(placeholder, 123)).toThrow();
|
||||
|
||||
expect(() =>
|
||||
hash(placeholder, {
|
||||
// @ts-expect-error
|
||||
toString() {
|
||||
return "scrypt";
|
||||
},
|
||||
}),
|
||||
).toThrow();
|
||||
|
||||
expect(() =>
|
||||
hash(placeholder, {
|
||||
// @ts-expect-error
|
||||
algorithm: "poop",
|
||||
}),
|
||||
).toThrow();
|
||||
|
||||
expect(() =>
|
||||
hash(placeholder, {
|
||||
algorithm: "bcrypt",
|
||||
cost: Infinity,
|
||||
}),
|
||||
).toThrow();
|
||||
|
||||
expect(() =>
|
||||
hash(placeholder, {
|
||||
algorithm: "argon2id",
|
||||
memoryCost: -1,
|
||||
}),
|
||||
).toThrow();
|
||||
|
||||
expect(() =>
|
||||
hash(placeholder, {
|
||||
algorithm: "argon2id",
|
||||
timeCost: -1,
|
||||
}),
|
||||
).toThrow();
|
||||
|
||||
expect(() =>
|
||||
hash(placeholder, {
|
||||
algorithm: "bcrypt",
|
||||
cost: -999,
|
||||
}),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
test("coercion throwing doesn't crash", () => {
|
||||
// @ts-expect-error
|
||||
expect(() => hash(Symbol())).toThrow();
|
||||
expect(() =>
|
||||
// @ts-expect-error
|
||||
hash({
|
||||
toString() {
|
||||
throw new Error("toString() failed");
|
||||
},
|
||||
}),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
for (let ArrayBufferView of [
|
||||
Uint8Array,
|
||||
Uint16Array,
|
||||
Uint32Array,
|
||||
Int8Array,
|
||||
Int16Array,
|
||||
Int32Array,
|
||||
Float32Array,
|
||||
Float64Array,
|
||||
ArrayBuffer,
|
||||
]) {
|
||||
test(`empty ${ArrayBufferView.name} throws`, () => {
|
||||
expect(() => hash(new ArrayBufferView(0))).toThrow("password must not be empty");
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("verify", () => {
|
||||
describe("arguments parsing", () => {
|
||||
for (let { verify } of [password, passwordSync]) {
|
||||
test("minimum args", () => {
|
||||
// @ts-expect-error
|
||||
expect(() => verify()).toThrow();
|
||||
// @ts-expect-error
|
||||
expect(() => verify("")).toThrow();
|
||||
});
|
||||
|
||||
test("empty values return false", async () => {
|
||||
expect(await verify("", "$")).toBeFalse();
|
||||
expect(await verify("$", "")).toBeFalse();
|
||||
});
|
||||
|
||||
test("invalid algorithm throws", () => {
|
||||
// @ts-expect-error
|
||||
expect(() => verify(placeholder, "$", "scrpyt")).toThrow();
|
||||
// @ts-expect-error
|
||||
expect(() => verify(placeholder, "$", 123)).toThrow();
|
||||
expect(() =>
|
||||
// @ts-expect-error
|
||||
verify(placeholder, "$", {
|
||||
toString() {
|
||||
return "scrypt";
|
||||
},
|
||||
}),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
test("coercion throwing doesn't crash", () => {
|
||||
// @ts-expect-error
|
||||
expect(() => verify(Symbol(), Symbol())).toThrow();
|
||||
expect(() =>
|
||||
verify(
|
||||
// @ts-expect-error
|
||||
{
|
||||
toString() {
|
||||
throw new Error("toString() failed");
|
||||
},
|
||||
},
|
||||
"valid",
|
||||
),
|
||||
).toThrow();
|
||||
expect(() =>
|
||||
// @ts-expect-error
|
||||
verify("valid", {
|
||||
toString() {
|
||||
throw new Error("toString() failed");
|
||||
},
|
||||
}),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
for (let ArrayBufferView of [
|
||||
Uint8Array,
|
||||
Uint16Array,
|
||||
Uint32Array,
|
||||
Int8Array,
|
||||
Int16Array,
|
||||
Int32Array,
|
||||
Float32Array,
|
||||
Float64Array,
|
||||
ArrayBuffer,
|
||||
]) {
|
||||
test(`empty ${ArrayBufferView.name} returns false`, async () => {
|
||||
expect(await verify(new ArrayBufferView(0), new ArrayBufferView(0))).toBeFalse();
|
||||
expect(await verify("", new ArrayBufferView(0))).toBeFalse();
|
||||
expect(await verify(new ArrayBufferView(0), "")).toBeFalse();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test("bcrypt longer than 72 characters is the SHA-512", async () => {
|
||||
const boop = Buffer.from("hey".repeat(100));
|
||||
const hashed = await password.hash(boop, "bcrypt");
|
||||
expect(await password.verify(Bun.SHA512.hash(boop), hashed, "bcrypt")).toBeTrue();
|
||||
});
|
||||
|
||||
test("bcrypt shorter than 72 characters is NOT the SHA-512", async () => {
|
||||
const boop = Buffer.from("hey".repeat(3));
|
||||
const hashed = await password.hash(boop, "bcrypt");
|
||||
expect(await password.verify(Bun.SHA512.hash(boop), hashed, "bcrypt")).toBeFalse();
|
||||
});
|
||||
|
||||
const defaultAlgorithm = "argon2id";
|
||||
const algorithms = [undefined, "argon2id", "bcrypt"];
|
||||
const argons = ["argon2i", "argon2id", "argon2d"];
|
||||
|
||||
for (let algorithmValue of algorithms) {
|
||||
const prefix = algorithmValue === "bcrypt" ? "$2" : "$" + (algorithmValue || defaultAlgorithm);
|
||||
|
||||
describe(algorithmValue ? algorithmValue : "default", () => {
|
||||
const hash = (value: string | TypedArray) => {
|
||||
return algorithmValue ? passwordSync.hash(value, algorithmValue as any) : passwordSync.hash(value);
|
||||
};
|
||||
|
||||
const hashSync = (value: string | TypedArray) => {
|
||||
return algorithmValue ? passwordSync.hash(value, algorithmValue as any) : passwordSync.hash(value);
|
||||
};
|
||||
|
||||
const verify = (pw: string | TypedArray, value: string | TypedArray) => {
|
||||
return algorithmValue ? password.verify(pw, value, algorithmValue as any) : password.verify(pw, value);
|
||||
};
|
||||
|
||||
const verifySync = (pw: string | TypedArray, value: string | TypedArray) => {
|
||||
return algorithmValue ? passwordSync.verify(pw, value, algorithmValue as any) : passwordSync.verify(pw, value);
|
||||
};
|
||||
|
||||
for (let input of [placeholder, Buffer.from(placeholder)]) {
|
||||
describe(typeof input === "string" ? "string" : "buffer", () => {
|
||||
test("passwordSync", () => {
|
||||
const hashed = hashSync(input);
|
||||
expect(hashed).toStartWith(prefix);
|
||||
expect(verifySync(input, hashed)).toBeTrue();
|
||||
expect(() => verifySync(hashed, input)).toThrow();
|
||||
expect(verifySync(input + "\0", hashed)).toBeFalse();
|
||||
});
|
||||
|
||||
test("password", async () => {
|
||||
async function runSlowTest(algorithm = algorithmValue as any) {
|
||||
const hashed = await password.hash(input, algorithm);
|
||||
const prefix = "$" + algorithm;
|
||||
expect(hashed).toStartWith(prefix);
|
||||
expect(await password.verify(input, hashed, algorithm)).toBeTrue();
|
||||
expect(() => password.verify(hashed, input, algorithm)).toThrow();
|
||||
expect(await password.verify(input + "\0", hashed, algorithm)).toBeFalse();
|
||||
}
|
||||
|
||||
async function runSlowTestWithOptions(algorithmLabel: any) {
|
||||
const algorithm = { algorithm: algorithmLabel, timeCost: 5, memoryCost: 4 };
|
||||
const hashed = await password.hash(input, algorithm);
|
||||
const prefix = "$" + algorithmLabel;
|
||||
expect(hashed).toStartWith(prefix);
|
||||
expect(hashed).toContain("t=5");
|
||||
expect(hashed).toContain("m=4");
|
||||
expect(await password.verify(input, hashed, algorithmLabel)).toBeTrue();
|
||||
expect(() => password.verify(hashed, input, algorithmLabel)).toThrow();
|
||||
expect(await password.verify(input + "\0", hashed, algorithmLabel)).toBeFalse();
|
||||
}
|
||||
|
||||
async function runSlowBCryptTest() {
|
||||
const algorithm = { algorithm: "bcrypt", cost: 4 } as const;
|
||||
const hashed = await password.hash(input, algorithm);
|
||||
const prefix = "$" + "2b";
|
||||
expect(hashed).toStartWith(prefix);
|
||||
expect(await password.verify(input, hashed, "bcrypt")).toBeTrue();
|
||||
expect(() => password.verify(hashed, input, "bcrypt")).toThrow();
|
||||
expect(await password.verify(input + "\0", hashed, "bcrypt")).toBeFalse();
|
||||
}
|
||||
|
||||
if (algorithmValue === defaultAlgorithm) {
|
||||
// these tests are very slow
|
||||
// run the hashing tests in parallel
|
||||
await Promise.all([...argons.map(runSlowTest), ...argons.map(runSlowTestWithOptions)]);
|
||||
return;
|
||||
}
|
||||
|
||||
async function defaultTest() {
|
||||
const hashed = await hash(input);
|
||||
expect(hashed).toStartWith(prefix);
|
||||
expect(await verify(input, hashed)).toBeTrue();
|
||||
expect(() => verify(hashed, input)).toThrow();
|
||||
expect(await verify(input + "\0", hashed)).toBeFalse();
|
||||
}
|
||||
|
||||
if (algorithmValue === "bcrypt") {
|
||||
await Promise.all([defaultTest(), runSlowBCryptTest()]);
|
||||
} else {
|
||||
await defaultTest();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -74,6 +74,27 @@ function testRunInContext(
|
||||
const result = fn("1 + 1; 2 * 2; 3 / 3", context);
|
||||
expect(result).toBe(1);
|
||||
});
|
||||
|
||||
for (let View of [
|
||||
ArrayBuffer,
|
||||
SharedArrayBuffer,
|
||||
Uint8Array,
|
||||
Int8Array,
|
||||
Uint16Array,
|
||||
Int16Array,
|
||||
Uint32Array,
|
||||
Int32Array,
|
||||
Float32Array,
|
||||
Float64Array,
|
||||
BigInt64Array,
|
||||
BigUint64Array,
|
||||
]) {
|
||||
test(`new ${View.name}() in VM context doesn't crash`, () => {
|
||||
const context = createContext({});
|
||||
expect(fn(`new ${View.name}(2)`, context)).toHaveLength(2);
|
||||
});
|
||||
}
|
||||
|
||||
test("can return a function", () => {
|
||||
const context = createContext({});
|
||||
const result = fn("() => 'bar';", context);
|
||||
|
||||
@@ -2906,6 +2906,47 @@ console.log(foo, array);
|
||||
});
|
||||
});
|
||||
|
||||
it("raw template literal contents", () => {
|
||||
expectPrinted("String.raw`\r`", "String.raw`\n`");
|
||||
expectPrinted("String.raw`\r\n`", "String.raw`\n`");
|
||||
expectPrinted("String.raw`\n`", "String.raw`\n`");
|
||||
expectPrinted("String.raw`\r\r\r\r\r\n\r`", "String.raw`\n\n\n\n\n\n`");
|
||||
expectPrinted("String.raw`\n\r`", "String.raw`\n\n`");
|
||||
var code = `String.raw\`
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>${"meow123"}</title>
|
||||
<link rel="stylesheet" href="/css/style.css" />
|
||||
</head>
|
||||
\``;
|
||||
var result = code;
|
||||
expectPrinted(code, code);
|
||||
|
||||
code = `String.raw\`
|
||||
<head>\r\n\n\r\r\r
|
||||
<meta charset="UTF-8" />\r
|
||||
<title>${"meow123"}</title>\n
|
||||
|
||||
\r
|
||||
\r
|
||||
\n\r
|
||||
<link rel="stylesheet" href="/css/style.css" />
|
||||
</head>
|
||||
\``;
|
||||
result = `String.raw\`
|
||||
<head>\n\n\n\n
|
||||
<meta charset="UTF-8" />
|
||||
<title>${"meow123"}</title>\n
|
||||
|
||||
|
||||
|
||||
\n
|
||||
<link rel="stylesheet" href="/css/style.css" />
|
||||
</head>
|
||||
\``;
|
||||
expectPrinted(code, result);
|
||||
});
|
||||
|
||||
describe("scan", () => {
|
||||
it("reports all export names", () => {
|
||||
const { imports, exports } = transpiler.scan(code);
|
||||
|
||||
Reference in New Issue
Block a user