From 565d1689e9f2c1f6b657f14b07fa9b95e50a5a56 Mon Sep 17 00:00:00 2001 From: Ai Hoshino Date: Sun, 9 Jul 2023 22:20:52 +0800 Subject: fix metadata bits of uuid (`randomUUID()`) (#3583) * fix uuid version Close: https://github.com/oven-sh/bun/issues/3575 * add unittest * small fix * avoid unnecessary copying --- src/bun.js/rare_data.zig | 8 ++++++-- src/bun.js/uuid.zig | 10 ++++++++++ src/bun.js/webcore.zig | 11 ++++------- src/bun.js/webcore/blob.zig | 2 +- src/http/websocket_http_client.zig | 2 +- test/js/web/web-globals.test.js | 19 +++++++++++++++++++ 6 files changed, 41 insertions(+), 11 deletions(-) diff --git a/src/bun.js/rare_data.zig b/src/bun.js/rare_data.zig index ddda96bf4..3b29896a4 100644 --- a/src/bun.js/rare_data.zig +++ b/src/bun.js/rare_data.zig @@ -9,6 +9,7 @@ const std = @import("std"); const BoringSSL = @import("root").bun.BoringSSL; const bun = @import("root").bun; const WebSocketClientMask = @import("../http/websocket_http_client.zig").Mask; +const UUID = @import("./uuid.zig"); boring_ssl_engine: ?*BoringSSL.ENGINE = null, editor_context: EditorContext = EditorContext{}, @@ -47,13 +48,16 @@ pub fn filePolls(this: *RareData, vm: *JSC.VirtualMachine) *JSC.FilePoll.HiveArr }; } -pub fn nextUUID(this: *RareData) [16]u8 { +pub fn nextUUID(this: *RareData) UUID { if (this.entropy_cache == null) { this.entropy_cache = default_allocator.create(EntropyCache) catch unreachable; this.entropy_cache.?.init(); } - return this.entropy_cache.?.get(); + this.entropy_cache.?.fill(); + + const bytes = this.entropy_cache.?.get(); + return UUID.initWith(&bytes); } pub fn entropySlice(this: *RareData, len: usize) []u8 { diff --git a/src/bun.js/uuid.zig b/src/bun.js/uuid.zig index e8bdff661..e38ed567f 100644 --- a/src/bun.js/uuid.zig +++ b/src/bun.js/uuid.zig @@ -18,6 +18,16 @@ pub fn init() UUID { uuid.bytes[6] = (uuid.bytes[6] & 0x0f) | 0x40; // Variant 1 uuid.bytes[8] = (uuid.bytes[8] & 0x3f) | 0x80; + + return uuid; +} + +pub fn initWith(bytes: *const [16]u8) UUID { + var uuid = UUID{ .bytes = bytes.* }; + + uuid.bytes[6] = (uuid.bytes[6] & 0x0f) | 0x40; + uuid.bytes[8] = (uuid.bytes[8] & 0x3f) | 0x80; + return uuid; } diff --git a/src/bun.js/webcore.zig b/src/bun.js/webcore.zig index fd69b0262..8cb9ec80a 100644 --- a/src/bun.js/webcore.zig +++ b/src/bun.js/webcore.zig @@ -366,7 +366,6 @@ pub const Prompt = struct { }; pub const Crypto = struct { - const UUID = @import("./uuid.zig"); const BoringSSL = @import("root").bun.BoringSSL; pub const Class = JSC.NewClass( void, @@ -693,9 +692,8 @@ pub const Crypto = struct { _: []const JSC.JSValue, ) JSC.JSValue { var out: [36]u8 = undefined; - const uuid: UUID = .{ - .bytes = globalThis.bunVM().rareData().nextUUID(), - }; + const uuid = globalThis.bunVM().rareData().nextUUID(); + uuid.print(&out); return JSC.ZigString.init(&out).toValueGC(globalThis); } @@ -723,9 +721,8 @@ pub const Crypto = struct { _: *anyopaque, ) callconv(.C) JSC.JSValue { var out: [36]u8 = undefined; - const uuid: UUID = .{ - .bytes = globalThis.bunVM().rareData().nextUUID(), - }; + const uuid = globalThis.bunVM().rareData().nextUUID(); + uuid.print(&out); return JSC.ZigString.init(&out).toValueGC(globalThis); } diff --git a/src/bun.js/webcore/blob.zig b/src/bun.js/webcore/blob.zig index 86b5414e3..ef2520049 100644 --- a/src/bun.js/webcore/blob.zig +++ b/src/bun.js/webcore/blob.zig @@ -249,7 +249,7 @@ pub const Blob = struct { var hex_buf: [70]u8 = undefined; const boundary = brk: { - var random = globalThis.bunVM().rareData().nextUUID(); + var random = globalThis.bunVM().rareData().nextUUID().bytes; var formatter = std.fmt.fmtSliceHexLower(&random); break :brk std.fmt.bufPrint(&hex_buf, "-WebkitFormBoundary{any}", .{formatter}) catch unreachable; }; diff --git a/src/http/websocket_http_client.zig b/src/http/websocket_http_client.zig index f495af3b7..ae8e40763 100644 --- a/src/http/websocket_http_client.zig +++ b/src/http/websocket_http_client.zig @@ -60,7 +60,7 @@ fn buildRequestBody( extra_headers: NonUTF8Headers, ) std.mem.Allocator.Error![]u8 { const allocator = vm.allocator; - const input_rand_buf = vm.rareData().nextUUID(); + const input_rand_buf = vm.rareData().nextUUID().bytes; const temp_buf_size = comptime std.base64.standard.Encoder.calcSize(16); var encoded_buf: [temp_buf_size]u8 = undefined; const accept_key = std.base64.standard.Encoder.encode(&encoded_buf, &input_rand_buf); diff --git a/test/js/web/web-globals.test.js b/test/js/web/web-globals.test.js index b7a243190..d687a1290 100644 --- a/test/js/web/web-globals.test.js +++ b/test/js/web/web-globals.test.js @@ -138,6 +138,25 @@ it("crypto.randomUUID", () => { }); }); +it("crypto.randomUUID version, issues#3575", () => { + var uuid = crypto.randomUUID(); + + function validate(uuid) { + const regex = + /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i; + return typeof uuid === "string" && regex.test(uuid); + } + function version(uuid) { + if (!validate(uuid)) { + throw TypeError("Invalid UUID"); + } + + return parseInt(uuid.slice(14, 15), 16); + } + + expect(version(uuid)).toBe(4); +}); + it("URL.prototype.origin", () => { const url = new URL("https://html.spec.whatwg.org/"); const { origin, host, hostname } = url; -- cgit v1.2.3