From ddd0a42b26de9688de8e29d80fa90951850a12a1 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Sat, 3 May 2025 07:52:12 +0800 Subject: [PATCH] add crypto web api --- src/browser/crypto/crypto.zig | 82 +++++++++++++++++++++++++++++++++++ src/browser/env.zig | 1 + src/browser/html/window.zig | 6 +++ src/runtime/js.zig | 9 +++- 4 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 src/browser/crypto/crypto.zig diff --git a/src/browser/crypto/crypto.zig b/src/browser/crypto/crypto.zig new file mode 100644 index 00000000..91bc8882 --- /dev/null +++ b/src/browser/crypto/crypto.zig @@ -0,0 +1,82 @@ +// Copyright (C) 2023-2024 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +const std = @import("std"); +const uuidv4 = @import("../../id.zig").uuidv4; + +// https://w3c.github.io/webcrypto/#crypto-interface +pub const Crypto = struct { + pub fn _getRandomValues(_: *const Crypto, into: RandomValues) !void { + const buf = into.asBuffer(); + if (buf.len > 65_536) { + return error.QuotaExceededError; + } + std.crypto.random.bytes(buf); + } + + pub fn _randomUUID(_: *const Crypto) [36]u8 { + var hex: [36]u8 = undefined; + uuidv4(&hex); + return hex; + } +}; + +const RandomValues = union(enum) { + int8: []i8, + uint8: []u8, + int16: []i16, + uint16: []u16, + int32: []i32, + uint32: []u32, + int64: []i64, + uint64: []u64, + + fn asBuffer(self: RandomValues) []u8 { + switch (self) { + .int8 => |b| return (@as([]u8, @ptrCast(b)))[0..b.len], + .uint8 => |b| return (@as([]u8, @ptrCast(b)))[0..b.len], + .int16 => |b| return (@as([]u8, @ptrCast(b)))[0 .. b.len * 2], + .uint16 => |b| return (@as([]u8, @ptrCast(b)))[0 .. b.len * 2], + .int32 => |b| return (@as([]u8, @ptrCast(b)))[0 .. b.len * 4], + .uint32 => |b| return (@as([]u8, @ptrCast(b)))[0 .. b.len * 4], + .int64 => |b| return (@as([]u8, @ptrCast(b)))[0 .. b.len * 8], + .uint64 => |b| return (@as([]u8, @ptrCast(b)))[0 .. b.len * 8], + } + } +}; + +const testing = @import("../../testing.zig"); +test "Browser.Crypto" { + var runner = try testing.jsRunner(testing.tracking_allocator, .{}); + defer runner.deinit(); + + try runner.testCases(&.{ + .{ "const a = crypto.randomUUID();", "undefined" }, + .{ "const b = crypto.randomUUID();", "undefined" }, + .{ "a.length;", "36" }, + .{ "a.length;", "36" }, + .{ "a == b;", "false" }, + }, .{}); + + try runner.testCases(&.{ + .{ "try { crypto.getRandomValues(new BigUint64Array(8193)) } catch(e) { e.message == 'QuotaExceededError' }", "true" }, + .{ "let r1 = new Int32Array(5)", "undefined" }, + .{ "crypto.getRandomValues(r1)", "undefined" }, + .{ "new Set(r1).size", "5" }, + }, .{}); +} diff --git a/src/browser/env.zig b/src/browser/env.zig index 80e0ecbc..ffb78a48 100644 --- a/src/browser/env.zig +++ b/src/browser/env.zig @@ -10,6 +10,7 @@ const HttpClient = @import("../http/client.zig").Client; const Renderer = @import("browser.zig").Renderer; const Interfaces = generate.Tuple(.{ + @import("crypto/crypto.zig").Crypto, @import("console/console.zig").Console, @import("dom/dom.zig").Interfaces, @import("events/event.zig").Interfaces, diff --git a/src/browser/html/window.zig b/src/browser/html/window.zig index 45e7d045..4d95b5a0 100644 --- a/src/browser/html/window.zig +++ b/src/browser/html/window.zig @@ -25,6 +25,7 @@ const SessionState = @import("../env.zig").SessionState; const Navigator = @import("navigator.zig").Navigator; const History = @import("history.zig").History; const Location = @import("location.zig").Location; +const Crypto = @import("../crypto/crypto.zig").Crypto; const Console = @import("../console/console.zig").Console; const EventTarget = @import("../dom/event_target.zig").EventTarget; @@ -49,6 +50,7 @@ pub const Window = struct { timeoutid: u32 = 0, timeoutids: [512]u64 = undefined, + crypto: Crypto = .{}, console: Console = .{}, navigator: Navigator = .{}, @@ -91,6 +93,10 @@ pub const Window = struct { return &self.console; } + pub fn get_crypto(self: *Window) *Crypto { + return &self.crypto; + } + pub fn get_self(self: *Window) *Window { return self; } diff --git a/src/runtime/js.zig b/src/runtime/js.zig index f501eac6..706284fe 100644 --- a/src/runtime/js.zig +++ b/src/runtime/js.zig @@ -2532,9 +2532,14 @@ fn Caller(comptime E: type) type { // We settle for just probing the first value. Ok, actually // not tricky in this case either. - const context = self.contxt; + const context = self.context; const js_obj = js_arr.castTo(v8.Object); - return self.probeJsValueToZig(named_function, ptr.child, try js_obj.getAtIndex(context, 0)); + switch (try self.probeJsValueToZig(named_function, ptr.child, try js_obj.getAtIndex(context, 0))) { + .value, .ok => return .{ .ok = {} }, + .compatible => return .{ .compatible = {} }, + .coerce => return .{ .coerce = {} }, + .invalid => return .{ .invalid = {} }, + } }, else => {}, },