mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-09-17 08:27:11 -04:00
chore: leverage bridge for error rejection
https://github.com/lightpanda-io/browser/pull/3095 made the bridge more promise- aware. A WebAPI with a `!js.Promise` return type that returns an error will not reject the promise. That PR was limited to Crypto. This expands it where possible. Generally speaking, the result is just more idiomatic Zig code. From: ```zig validateName(name) catch |err| switch (err) { error.SyntaxError => return local.rejectPromise(.{ .dom_exception = .{ .err = error.SyntaxError } }), } ``` to: ```zig try validateName(name); ``` However, because TypeErrors must often reject with a specific message, we use a pseudo-global in Env. So, you'd do: ```zig .invalid => return local.typeError("invalid algorithm"), ``` which return error.TypeError AND stores "invalid algorithm" on the `Env`. When `Caller` gets `error.TypeError` it checks the Env for a message. Who says Zig errors can't carry payloads?! ;# Please enter the commit message for your changes. Lines starting
This commit is contained in:
13 files changed
+113
-186
No files matched your search
@@ -585,6 +585,9 @@ fn handleError(comptime T: type, comptime F: type, local: *const Local, err: any
|
||||
}
|
||||
|
||||
const err_local = errorLocal(T, local, info);
|
||||
const env = local.ctx.env;
|
||||
const message = env.error_message orelse "";
|
||||
env.error_message = null;
|
||||
|
||||
const js_err: *const v8.Value = blk: {
|
||||
// Error constructors use the isolate's current context: enter the
|
||||
@@ -595,7 +598,7 @@ fn handleError(comptime T: type, comptime F: type, local: *const Local, err: any
|
||||
|
||||
break :blk switch (err) {
|
||||
error.InvalidArgument => isolate.createTypeError("invalid argument"),
|
||||
error.TypeError => isolate.createTypeError(""),
|
||||
error.TypeError => isolate.createTypeError(message),
|
||||
error.RangeError => isolate.createRangeError(""),
|
||||
error.OutOfMemory => isolate.createError("out of memory"),
|
||||
error.IllegalConstructor => isolate.createError("Illegal Constructor"),
|
||||
|
||||
@@ -109,6 +109,10 @@ tearing_down: bool = false,
|
||||
|
||||
heap_limit_protected: bool = false,
|
||||
|
||||
// Message for the next TypeError the bridge builds. Set by local.typeError.
|
||||
// Think of it as our own little global errno. How cute.
|
||||
error_message: ?[]const u8 = null,
|
||||
|
||||
pub const InitOpts = struct {
|
||||
with_inspector: bool = false,
|
||||
};
|
||||
|
||||
@@ -1512,6 +1512,15 @@ pub fn stackTrace(self: *const Local) !?[]const u8 {
|
||||
return buf.written();
|
||||
}
|
||||
|
||||
// We sometimes need to reject with a specific TypeError message. We can't
|
||||
// attach an anything to `error.TypeError`, but we can use a pseudo-global.
|
||||
// When caller catches the error.TypeError, it'll look into env.error_message
|
||||
// for the message.
|
||||
pub fn typeError(self: *const Local, message: []const u8) error{TypeError} {
|
||||
self.ctx.env.error_message = message;
|
||||
return error.TypeError;
|
||||
}
|
||||
|
||||
// == Promise Helpers ==
|
||||
pub fn rejectPromise(self: *const Local, err: js.PromiseResolver.RejectError) js.Promise {
|
||||
var resolver = js.PromiseResolver.init(self);
|
||||
|
||||
@@ -291,9 +291,10 @@
|
||||
await response1.text();
|
||||
const usedAfter1 = response1.bodyUsed;
|
||||
|
||||
// Re-consuming a used body rejects.
|
||||
let rejected1 = false;
|
||||
try { await response1.text(); } catch (e) { rejected1 = true; }
|
||||
// Re-consuming a used body rejects with a TypeError (a rejection, not a
|
||||
// synchronous throw), carrying the message the bridge was handed.
|
||||
let rejected1 = null;
|
||||
try { await response1.text(); } catch (e) { rejected1 = e; }
|
||||
|
||||
// A bodyless response is never "used".
|
||||
const response2 = new Response();
|
||||
@@ -309,7 +310,8 @@
|
||||
await state.done(() => {
|
||||
testing.expectFalse(usedBefore1);
|
||||
testing.expectTrue(usedAfter1);
|
||||
testing.expectTrue(rejected1);
|
||||
testing.expectEqual('TypeError', rejected1.constructor.name);
|
||||
testing.expectEqual('Body has already been read', rejected1.message);
|
||||
|
||||
testing.expectFalse(usedBefore2);
|
||||
testing.expectFalse(usedAfter2);
|
||||
|
||||
@@ -157,9 +157,7 @@ pub fn whenDefined(self: *CustomElementRegistry, name: []const u8, frame: *Frame
|
||||
return local.resolvePromise(definition.constructor);
|
||||
}
|
||||
|
||||
validateName(name) catch |err| switch (err) {
|
||||
error.SyntaxError => return local.rejectPromise(.{ .dom_exception = .{ .err = error.SyntaxError } }),
|
||||
};
|
||||
try validateName(name);
|
||||
|
||||
const gop = try self._when_defined.getOrPut(frame.arena, name);
|
||||
if (gop.found_existing) {
|
||||
|
||||
@@ -59,17 +59,15 @@ pub fn generateKey(
|
||||
.aes_key_gen => |params| return AES.generate(params, extractable, key_usages, exec),
|
||||
.ec_key_gen => |params| return EC.generate(params, extractable, key_usages, exec),
|
||||
.rsa_hashed_key_gen => |params| {
|
||||
RSA.validate(params, key_usages) catch |err| {
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = err } });
|
||||
};
|
||||
try RSA.validate(params, key_usages);
|
||||
log.warn(.not_implemented, "generateKey", .{ .name = params.name });
|
||||
},
|
||||
.name => |js_name| return generateKeyFromName(try js_name.toSSO(false), extractable, key_usages, exec),
|
||||
.object => |object| return generateKeyFromName(try object.name.toSSO(false), extractable, key_usages, exec),
|
||||
.invalid => return local.rejectPromise(.{ .type_error = "invalid algorithm" }),
|
||||
.invalid => return local.typeError("invalid algorithm"),
|
||||
}
|
||||
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } });
|
||||
return error.NotSupported;
|
||||
}
|
||||
|
||||
fn generateKeyFromName(
|
||||
@@ -77,17 +75,6 @@ fn generateKeyFromName(
|
||||
extractable: bool,
|
||||
key_usages: []const []const u8,
|
||||
exec: *const Execution,
|
||||
) !js.Promise {
|
||||
return _generateKeyFromName(name, extractable, key_usages, exec) catch |err| {
|
||||
return exec.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = err } });
|
||||
};
|
||||
}
|
||||
|
||||
fn _generateKeyFromName(
|
||||
name: String,
|
||||
extractable: bool,
|
||||
key_usages: []const []const u8,
|
||||
exec: *const Execution,
|
||||
) !js.Promise {
|
||||
if (name.eql(comptime .wrap("X25519"))) {
|
||||
return X25519.init(extractable, key_usages, exec);
|
||||
@@ -150,18 +137,16 @@ pub fn importKey(
|
||||
const is_private = importKind(format, key_data);
|
||||
if (asymmetricAllowedUsages(name, is_private)) |allowed| {
|
||||
// Public keys may have empty usages; secret/private keys may not.
|
||||
const mask = common.usageMaskInner(allowed, key_usages, is_private) catch |err| {
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = err } });
|
||||
};
|
||||
const mask = try common.usageMaskInner(allowed, key_usages, is_private);
|
||||
if (EC.canonicalName(name) != null) {
|
||||
const der = switch (key_data) {
|
||||
.bytes => |b| b.values,
|
||||
.jwk => return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } }),
|
||||
.jwk => return error.NotSupported,
|
||||
};
|
||||
return EC.import(name, algo.namedCurve(), format, der, is_private, extractable, mask, exec);
|
||||
}
|
||||
log.warn(.not_implemented, "SubtleCrypto.importKey", .{ .name = name });
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } });
|
||||
return error.NotSupported;
|
||||
}
|
||||
|
||||
// Resolve the raw key bytes from the requested format. Symmetric keys
|
||||
@@ -171,27 +156,22 @@ pub fn importKey(
|
||||
break :blk switch (key_data) {
|
||||
.bytes => |b| b.values,
|
||||
// A JWK object passed where a BufferSource is expected.
|
||||
.jwk => return local.rejectPromise(.{ .type_error = "raw format expects a BufferSource" }),
|
||||
.jwk => return local.typeError("raw format expects a BufferSource"),
|
||||
};
|
||||
}
|
||||
if (std.mem.eql(u8, format, "jwk")) {
|
||||
const jwk = switch (key_data) {
|
||||
.jwk => |j| j,
|
||||
.bytes => return local.rejectPromise(.{ .type_error = "jwk format expects an object" }),
|
||||
.bytes => return local.typeError("jwk format expects an object"),
|
||||
};
|
||||
if (!std.mem.eql(u8, jwk.kty, "oct")) {
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = error.DataError } });
|
||||
return error.DataError;
|
||||
}
|
||||
const k = jwk.k orelse {
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = error.DataError } });
|
||||
};
|
||||
break :blk common.base64Decode(exec.local_arena, k) catch |err| switch (err) {
|
||||
error.DataError => return local.rejectPromise(.{ .dom_exception = .{ .err = error.DataError } }),
|
||||
else => |e| return e,
|
||||
};
|
||||
const k = jwk.k orelse return error.DataError;
|
||||
break :blk try common.base64Decode(exec.local_arena, k);
|
||||
}
|
||||
// spki / pkcs8 (asymmetric formats) are not supported for these algorithms.
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } });
|
||||
return error.NotSupported;
|
||||
};
|
||||
|
||||
if (AES.canonicalName(name) != null) {
|
||||
@@ -202,9 +182,7 @@ pub fn importKey(
|
||||
// no length constraint and these keys are non-extractable.
|
||||
inline for ([_][]const u8{ "HKDF", "PBKDF2" }) |derive_name| {
|
||||
if (eqlIgnoreCase(name, derive_name)) {
|
||||
const mask = common.usageMask(&.{ "deriveKey", "deriveBits" }, key_usages) catch |err| {
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = err } });
|
||||
};
|
||||
const mask = try common.usageMask(&.{ "deriveKey", "deriveBits" }, key_usages);
|
||||
const crypto_key = try CryptoKey.init(exec, .{
|
||||
._type = .derive,
|
||||
._kind = .secret,
|
||||
@@ -222,13 +200,13 @@ pub fn importKey(
|
||||
.string => |s| s,
|
||||
.object => |o| o.name,
|
||||
},
|
||||
else => return local.rejectPromise(.{ .type_error = "HMAC import requires a hash" }),
|
||||
else => return local.typeError("HMAC import requires a hash"),
|
||||
};
|
||||
return HMAC.import(hash_name, raw, extractable, key_usages, exec);
|
||||
}
|
||||
|
||||
log.warn(.not_implemented, "SubtleCrypto.importKey", .{ .name = name });
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } });
|
||||
return error.NotSupported;
|
||||
}
|
||||
|
||||
/// Whether the requested format/key-data describe a private key. The format
|
||||
@@ -274,7 +252,7 @@ pub fn exportKey(
|
||||
) !js.Promise {
|
||||
const local = exec.js.local.?;
|
||||
if (!key.canExportKey()) {
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } });
|
||||
return error.InvalidAccessError;
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, format, "raw")) {
|
||||
@@ -288,10 +266,10 @@ pub fn exportKey(
|
||||
const is_unsupported = std.mem.eql(u8, format, "pkcs8") or std.mem.eql(u8, format, "spki");
|
||||
if (is_unsupported) {
|
||||
log.warn(.not_implemented, "SubtleCrypto.exportKey", .{ .format = format });
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } });
|
||||
return error.NotSupported;
|
||||
}
|
||||
|
||||
return local.rejectPromise(.{ .type_error = "invalid format" });
|
||||
return local.typeError("invalid format");
|
||||
}
|
||||
|
||||
/// The JSON Web Key returned for symmetric ("oct") keys.
|
||||
@@ -318,7 +296,7 @@ fn exportJwk(key: *CryptoKey, exec: *const Execution) !js.Promise {
|
||||
},
|
||||
else => {
|
||||
log.warn(.not_implemented, "SubtleCrypto.exportKey", .{ .format = "jwk", .type = key._type });
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } });
|
||||
return error.NotSupported;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -340,9 +318,7 @@ pub fn deriveBits(
|
||||
exec: *const Execution,
|
||||
) !js.Promise {
|
||||
const local = exec.js.local.?;
|
||||
const bits = deriveRaw(algo, base_key, length, base_key.canDeriveBits(), exec) catch |err| {
|
||||
return rejectDerive(local, err);
|
||||
};
|
||||
const bits = try deriveRaw(algo, base_key, length, base_key.canDeriveBits(), exec);
|
||||
return local.resolvePromise(js.ArrayBuffer{ .values = bits });
|
||||
}
|
||||
|
||||
@@ -357,35 +333,28 @@ pub fn deriveKey(
|
||||
key_usages: []const []const u8,
|
||||
exec: *const Execution,
|
||||
) !js.Promise {
|
||||
const local = exec.js.local.?;
|
||||
// The base key's deriveKey usage (not deriveBits) gates this operation.
|
||||
const usage_ok = base_key.canDeriveKey();
|
||||
|
||||
switch (derived) {
|
||||
.keyed => |k| {
|
||||
if (AES.canonicalName(k.name) == null) {
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } });
|
||||
return error.NotSupported;
|
||||
}
|
||||
const bits = deriveRaw(algo, base_key, k.length, usage_ok, exec) catch |err| {
|
||||
return rejectDerive(local, err);
|
||||
};
|
||||
const bits = try deriveRaw(algo, base_key, k.length, usage_ok, exec);
|
||||
return AES.import(k.name, bits, extractable, key_usages, exec);
|
||||
},
|
||||
.hmac => |h| {
|
||||
const hash_name = h.hash.name();
|
||||
const hash_md = crypto.findDigest(hash_name) catch {
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } });
|
||||
};
|
||||
const hash_md = crypto.findDigest(hash_name) catch return error.NotSupported;
|
||||
// Default length, per spec, is the hash's block size (in bits).
|
||||
const length: u32 = h.length orelse @intCast(crypto.EVP_MD_block_size(hash_md) * 8);
|
||||
const bits = deriveRaw(algo, base_key, length, usage_ok, exec) catch |err| {
|
||||
return rejectDerive(local, err);
|
||||
};
|
||||
const bits = try deriveRaw(algo, base_key, length, usage_ok, exec);
|
||||
return HMAC.import(hash_name, bits, extractable, key_usages, exec);
|
||||
},
|
||||
.object, .name => {
|
||||
log.warn(.not_implemented, "SubtleCrypto.deriveKey", .{});
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } });
|
||||
return error.NotSupported;
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -426,16 +395,6 @@ fn deriveRaw(
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps a KDF error to the spec-mandated DOMException (OutOfMemory propagates).
|
||||
fn rejectDerive(local: *const js.Local, err: KDF.Error) !js.Promise {
|
||||
return switch (err) {
|
||||
error.InvalidAccessError => local.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } }),
|
||||
error.NotSupported => local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } }),
|
||||
error.OperationError => local.rejectPromise(.{ .dom_exception = .{ .err = error.OperationError } }),
|
||||
error.OutOfMemory => error.OutOfMemory,
|
||||
};
|
||||
}
|
||||
|
||||
/// Encrypts data with the given key and algorithm.
|
||||
pub fn encrypt(
|
||||
_: *const SubtleCrypto,
|
||||
@@ -469,15 +428,10 @@ fn cryptOp(
|
||||
const params = switch (algo) {
|
||||
.params => |p| p,
|
||||
// A bare string identifier carries no iv/counter, so it can't drive AES.
|
||||
.name => return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } }),
|
||||
.name => return error.NotSupported,
|
||||
};
|
||||
|
||||
const out = AES.crypt(params, key, data, encrypting, exec) catch |err| switch (err) {
|
||||
error.InvalidAccessError => return local.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } }),
|
||||
error.OperationError => return local.rejectPromise(.{ .dom_exception = .{ .err = error.OperationError } }),
|
||||
error.NotSupported => return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } }),
|
||||
error.OutOfMemory => return error.OutOfMemory,
|
||||
};
|
||||
const out = try AES.crypt(params, key, data, encrypting, exec);
|
||||
return local.resolvePromise(js.ArrayBuffer{ .values = out });
|
||||
}
|
||||
|
||||
@@ -495,7 +449,7 @@ pub fn sign(
|
||||
.hmac => return HMAC.sign(algo, key, data, exec),
|
||||
else => {
|
||||
log.warn(.not_implemented, "SubtleCrypto.sign", .{ .key_type = key._type });
|
||||
return exec.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } });
|
||||
return error.InvalidAccessError;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -509,14 +463,13 @@ pub fn verify(
|
||||
data: []const u8, // ArrayBuffer.
|
||||
exec: *const Execution,
|
||||
) !js.Promise {
|
||||
const local = exec.js.local.?;
|
||||
if (!algo.isHMAC()) {
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } });
|
||||
return error.InvalidAccessError;
|
||||
}
|
||||
|
||||
return switch (key._type) {
|
||||
.hmac => HMAC.verify(key, signature, data, exec),
|
||||
else => local.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } }),
|
||||
else => error.InvalidAccessError,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -540,16 +493,14 @@ pub fn digest(_: *const SubtleCrypto, algo: DigestInput, data: js.TypedArray(u8)
|
||||
const local = exec.js.local.?;
|
||||
|
||||
const algo_name = algo.name() orelse {
|
||||
return local.rejectPromise(.{ .type_error = "required member name is undefined" });
|
||||
return local.typeError("required member name is undefined");
|
||||
};
|
||||
if (algo_name.len > 10) {
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } });
|
||||
return error.NotSupported;
|
||||
}
|
||||
|
||||
const normalized = std.ascii.upperString(exec.buf, algo_name);
|
||||
const digest_type = crypto.findDigest(normalized) catch {
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } });
|
||||
};
|
||||
const digest_type = crypto.findDigest(normalized) catch return error.NotSupported;
|
||||
|
||||
const bytes = data.values;
|
||||
const out = exec.buf[0..crypto.EVP_MAX_MD_SIZE];
|
||||
|
||||
@@ -85,9 +85,7 @@ pub fn generate(
|
||||
exec: *const Execution,
|
||||
) !js.Promise {
|
||||
const local = exec.js.local.?;
|
||||
validate(params, key_usages) catch |err| {
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = err } });
|
||||
};
|
||||
try validate(params, key_usages);
|
||||
|
||||
// validate() already confirmed the usages and length are well-formed.
|
||||
const allowed = allowedUsages(params.name).?;
|
||||
@@ -122,15 +120,13 @@ pub fn import(
|
||||
const local = exec.js.local.?;
|
||||
|
||||
const canonical = canonicalName(name) orelse {
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } });
|
||||
return error.NotSupported;
|
||||
};
|
||||
|
||||
const mask = common.usageMask(allowedUsages(name).?, key_usages) catch |err| {
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = err } });
|
||||
};
|
||||
const mask = try common.usageMask(allowedUsages(name).?, key_usages);
|
||||
|
||||
if (raw.len != 16 and raw.len != 24 and raw.len != 32) {
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = error.DataError } });
|
||||
return error.DataError;
|
||||
}
|
||||
|
||||
const crypto_key = try CryptoKey.init(exec, .{
|
||||
|
||||
@@ -94,9 +94,7 @@ pub fn generate(
|
||||
exec: *const Execution,
|
||||
) !js.Promise {
|
||||
const local = exec.js.local.?;
|
||||
validate(params, key_usages) catch |err| {
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = err } });
|
||||
};
|
||||
try validate(params, key_usages);
|
||||
|
||||
const name = canonicalName(params.name).?;
|
||||
const curve = curveCanonical(params.namedCurve).?;
|
||||
@@ -111,7 +109,7 @@ pub fn generate(
|
||||
const ec = crypto.EC_KEY_new_by_curve_name(nid) orelse return error.OutOfMemory;
|
||||
defer crypto.EC_KEY_free(ec);
|
||||
if (crypto.EC_KEY_generate_key(ec) != 1) {
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = error.OperationError } });
|
||||
return error.OperationError;
|
||||
}
|
||||
|
||||
const private_pkey = crypto.EVP_PKEY_new() orelse return error.OutOfMemory;
|
||||
@@ -167,28 +165,28 @@ pub fn import(
|
||||
|
||||
const canonical = canonicalName(name).?;
|
||||
const curve = curveCanonical(named_curve) orelse {
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } });
|
||||
return error.NotSupported;
|
||||
};
|
||||
|
||||
var ptr: [*c]const u8 = der.ptr;
|
||||
const pkey: *crypto.EVP_PKEY = blk: {
|
||||
if (std.mem.eql(u8, format, "spki")) {
|
||||
break :blk crypto.d2i_PUBKEY(null, &ptr, @intCast(der.len)) orelse {
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = error.DataError } });
|
||||
return error.DataError;
|
||||
};
|
||||
}
|
||||
if (std.mem.eql(u8, format, "pkcs8")) {
|
||||
break :blk crypto.d2i_AutoPrivateKey(null, &ptr, @intCast(der.len)) orelse {
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = error.DataError } });
|
||||
return error.DataError;
|
||||
};
|
||||
}
|
||||
// jwk / raw not implemented yet.
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } });
|
||||
return error.NotSupported;
|
||||
};
|
||||
errdefer crypto.EVP_PKEY_free(pkey);
|
||||
|
||||
if (crypto.EVP_PKEY_id(pkey) != crypto.EVP_PKEY_EC) {
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = error.DataError } });
|
||||
return error.DataError;
|
||||
}
|
||||
|
||||
const crypto_key = try CryptoKey.init(exec, .{
|
||||
|
||||
@@ -40,7 +40,7 @@ pub fn init(
|
||||
// The union probe match to get here is pretty simple, so we can end up here
|
||||
// for an unknown/invalid algo.
|
||||
if (!std.ascii.eqlIgnoreCase(params.name, "HMAC")) {
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } });
|
||||
return error.NotSupported;
|
||||
}
|
||||
const hash_name = switch (params.hash) {
|
||||
.string => |str| str,
|
||||
@@ -48,9 +48,7 @@ pub fn init(
|
||||
};
|
||||
// Per spec, an unrecognized hash is caught during algorithm normalization
|
||||
// and surfaces as NotSupportedError.
|
||||
const digest = crypto.findDigest(hash_name) catch return local.rejectPromise(.{
|
||||
.dom_exception = .{ .err = error.NotSupported },
|
||||
});
|
||||
const digest = crypto.findDigest(hash_name) catch return error.NotSupported;
|
||||
|
||||
// HMAC only accepts sign / verify; any other usage is a SyntaxError per
|
||||
// the spec, even when the entry exists elsewhere in CryptoKey.Usages.
|
||||
@@ -61,15 +59,11 @@ pub fn init(
|
||||
} else if (std.mem.eql(u8, usage, "verify")) {
|
||||
mask |= CryptoKey.Usages.verify;
|
||||
} else {
|
||||
return local.rejectPromise(.{
|
||||
.dom_exception = .{ .err = error.SyntaxError },
|
||||
});
|
||||
return error.SyntaxError;
|
||||
}
|
||||
}
|
||||
if (key_usages.len == 0) {
|
||||
return local.rejectPromise(.{
|
||||
.dom_exception = .{ .err = error.SyntaxError },
|
||||
});
|
||||
return error.SyntaxError;
|
||||
}
|
||||
|
||||
const block_size: usize = blk: {
|
||||
@@ -115,16 +109,12 @@ pub fn import(
|
||||
) !js.Promise {
|
||||
const local = exec.js.local.?;
|
||||
|
||||
const digest = crypto.findDigest(hash_name) catch return local.rejectPromise(.{
|
||||
.dom_exception = .{ .err = error.NotSupported },
|
||||
});
|
||||
const digest = crypto.findDigest(hash_name) catch return error.NotSupported;
|
||||
|
||||
const mask = common.usageMask(&.{ "sign", "verify" }, key_usages) catch |err| {
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = err } });
|
||||
};
|
||||
const mask = try common.usageMask(&.{ "sign", "verify" }, key_usages);
|
||||
|
||||
if (raw.len == 0) {
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = error.DataError } });
|
||||
return error.DataError;
|
||||
}
|
||||
|
||||
const crypto_key = try CryptoKey.init(exec, .{
|
||||
|
||||
@@ -42,9 +42,7 @@ pub fn init(
|
||||
// Calculate usages; only matters for private key.
|
||||
// Only deriveKey() and deriveBits() be used for X25519.
|
||||
if (key_usages.len == 0) {
|
||||
return local.rejectPromise(.{
|
||||
.dom_exception = .{ .err = error.SyntaxError },
|
||||
});
|
||||
return error.SyntaxError;
|
||||
}
|
||||
var mask: u8 = 0;
|
||||
iter_usages: for (key_usages) |usage| {
|
||||
@@ -55,9 +53,7 @@ pub fn init(
|
||||
}
|
||||
}
|
||||
// Unknown usage if got here.
|
||||
return local.rejectPromise(.{
|
||||
.dom_exception = .{ .err = error.SyntaxError },
|
||||
});
|
||||
return error.SyntaxError;
|
||||
}
|
||||
|
||||
const public_value = try exec.local_arena.alloc(u8, crypto.X25519_PUBLIC_VALUE_LEN);
|
||||
|
||||
@@ -237,24 +237,21 @@ pub fn getBodyUsed(self: *const Request) bool {
|
||||
return self._body_used;
|
||||
}
|
||||
|
||||
// Marks a present body consumed; returns a rejected promise if it already was.
|
||||
fn consume(self: *Request, local: *const js.Local) ?js.Promise {
|
||||
// Marks a present body consumed; a TypeError if it already was.
|
||||
fn consume(self: *Request, local: *const js.Local) !void {
|
||||
if (self._body == null) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self._body_used) {
|
||||
return local.rejectPromise(.{ .type_error = "Body has already been read" });
|
||||
return local.typeError("Body has already been read");
|
||||
}
|
||||
self._body_used = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn blob(self: *Request, exec: *const Execution) !js.Promise {
|
||||
const local = exec.js.local.?;
|
||||
if (self.consume(local)) |rejected| {
|
||||
return rejected;
|
||||
}
|
||||
try self.consume(local);
|
||||
|
||||
const body = self._body orelse "";
|
||||
const headers = try self.getHeaders(exec);
|
||||
@@ -266,17 +263,13 @@ pub fn blob(self: *Request, exec: *const Execution) !js.Promise {
|
||||
|
||||
pub fn text(self: *Request, exec: *const Execution) !js.Promise {
|
||||
const local = exec.js.local.?;
|
||||
if (self.consume(local)) |rejected| {
|
||||
return rejected;
|
||||
}
|
||||
try self.consume(local);
|
||||
return local.resolvePromise(body_init.stripUtf8Bom(self._body orelse ""));
|
||||
}
|
||||
|
||||
pub fn json(self: *Request, exec: *const Execution) !js.Promise {
|
||||
const local = exec.js.local.?;
|
||||
if (self.consume(local)) |rejected| {
|
||||
return rejected;
|
||||
}
|
||||
try self.consume(local);
|
||||
|
||||
const value = local.parseJSON(body_init.stripUtf8Bom(self._body orelse "")) catch {
|
||||
return local.rejectPromise(.{ .syntax_error = "failed to parse" });
|
||||
@@ -286,32 +279,26 @@ pub fn json(self: *Request, exec: *const Execution) !js.Promise {
|
||||
|
||||
pub fn arrayBuffer(self: *Request, exec: *const Execution) !js.Promise {
|
||||
const local = exec.js.local.?;
|
||||
if (self.consume(local)) |rejected| {
|
||||
return rejected;
|
||||
}
|
||||
try self.consume(local);
|
||||
return local.resolvePromise(js.ArrayBuffer{ .values = self._body orelse "" });
|
||||
}
|
||||
|
||||
pub fn bytes(self: *Request, exec: *const Execution) !js.Promise {
|
||||
const local = exec.js.local.?;
|
||||
if (self.consume(local)) |rejected| {
|
||||
return rejected;
|
||||
}
|
||||
try self.consume(local);
|
||||
return local.resolvePromise(js.TypedArray(u8){ .values = self._body orelse "" });
|
||||
}
|
||||
|
||||
pub fn formData(self: *Request, exec: *const Execution) !js.Promise {
|
||||
const local = exec.js.local.?;
|
||||
if (self.consume(local)) |rejected| {
|
||||
return rejected;
|
||||
}
|
||||
try self.consume(local);
|
||||
|
||||
// Per Fetch, a null body acts as an empty byte sequence.
|
||||
const body = self._body orelse "";
|
||||
|
||||
const headers = try self.getHeaders(exec);
|
||||
const content_type = try headers.get("content-type", exec) orelse {
|
||||
return local.rejectPromise(.{ .type_error = "Failed to parse body as FormData" });
|
||||
return local.typeError("Failed to parse body as FormData");
|
||||
};
|
||||
var it = ContentTypeIterator.init(content_type);
|
||||
const essence = it.essence;
|
||||
@@ -322,12 +309,12 @@ pub fn formData(self: *Request, exec: *const Execution) !js.Promise {
|
||||
if (std.ascii.eqlIgnoreCase(essence, "multipart/form-data")) {
|
||||
const boundary = it.findBoundary();
|
||||
if (boundary.len == 0) {
|
||||
return local.rejectPromise(.{ .type_error = "Failed to parse body as FormData" });
|
||||
return local.typeError("Failed to parse body as FormData");
|
||||
}
|
||||
|
||||
const form_data = FormData.initFromMultipart(body, boundary, exec) catch |err| switch (err) {
|
||||
error.OutOfMemory => return err,
|
||||
else => return local.rejectPromise(.{ .type_error = "Failed to parse body as FormData" }),
|
||||
else => return local.typeError("Failed to parse body as FormData"),
|
||||
};
|
||||
return local.resolvePromise(form_data);
|
||||
}
|
||||
@@ -335,12 +322,12 @@ pub fn formData(self: *Request, exec: *const Execution) !js.Promise {
|
||||
if (std.ascii.eqlIgnoreCase(essence, "application/x-www-form-urlencoded")) {
|
||||
const form_data = FormData.initFromUrlEncoded(body, exec) catch |err| switch (err) {
|
||||
error.OutOfMemory => return err,
|
||||
else => return local.rejectPromise(.{ .type_error = "Failed to parse body as FormData" }),
|
||||
else => return local.typeError("Failed to parse body as FormData"),
|
||||
};
|
||||
return local.resolvePromise(form_data);
|
||||
}
|
||||
|
||||
return local.rejectPromise(.{ .type_error = "Failed to parse body as FormData" });
|
||||
return local.typeError("Failed to parse body as FormData");
|
||||
}
|
||||
|
||||
pub fn clone(self: *const Request, exec: *const Execution) !*Request {
|
||||
|
||||
@@ -292,43 +292,38 @@ pub fn getBodyUsed(self: *const Response) bool {
|
||||
};
|
||||
}
|
||||
|
||||
// Marks a present body consumed; returns a rejected promise if it already was.
|
||||
fn consume(self: *Response, local: *const js.Local) ?js.Promise {
|
||||
// Marks a present body consumed; a TypeError if it already was.
|
||||
fn consume(self: *Response, local: *const js.Local) !void {
|
||||
switch (self._body) {
|
||||
.empty => return null,
|
||||
.empty => return,
|
||||
else => {},
|
||||
}
|
||||
if (self._body_used) {
|
||||
return local.rejectPromise(.{ .type_error = "Body has already been read" });
|
||||
return local.typeError("Body has already been read");
|
||||
}
|
||||
self._body_used = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn getText(self: *Response, exec: *const Execution) !js.Promise {
|
||||
const local = exec.js.local.?;
|
||||
if (self.consume(local)) |rejected| {
|
||||
return rejected;
|
||||
}
|
||||
try self.consume(local);
|
||||
|
||||
const body = switch (self._body) {
|
||||
.bytes => |b| body_init.stripUtf8Bom(b),
|
||||
.empty => "",
|
||||
.stream => return local.rejectPromise(.{ .type_error = "Cannot read text from stream body" }),
|
||||
.stream => return local.typeError("Cannot read text from stream body"),
|
||||
};
|
||||
return local.resolvePromise(body);
|
||||
}
|
||||
|
||||
pub fn getJson(self: *Response, exec: *const Execution) !js.Promise {
|
||||
const local = exec.js.local.?;
|
||||
if (self.consume(local)) |rejected| {
|
||||
return rejected;
|
||||
}
|
||||
try self.consume(local);
|
||||
|
||||
const body = switch (self._body) {
|
||||
.bytes => |b| body_init.stripUtf8Bom(b),
|
||||
.empty => "",
|
||||
.stream => return local.rejectPromise(.{ .type_error = "Cannot read JSON from stream body" }),
|
||||
.stream => return local.typeError("Cannot read JSON from stream body"),
|
||||
};
|
||||
const value = local.parseJSON(body) catch {
|
||||
return local.rejectPromise(.{ .syntax_error = "failed to parse" });
|
||||
@@ -338,9 +333,7 @@ pub fn getJson(self: *Response, exec: *const Execution) !js.Promise {
|
||||
|
||||
pub fn arrayBuffer(self: *Response, exec: *const Execution) !js.Promise {
|
||||
const local = exec.js.local.?;
|
||||
if (self.consume(local)) |rejected| {
|
||||
return rejected;
|
||||
}
|
||||
try self.consume(local);
|
||||
|
||||
return switch (self._body) {
|
||||
.bytes => |body| local.resolvePromise(js.ArrayBuffer{ .values = body }),
|
||||
@@ -464,11 +457,11 @@ const StreamConsumer = struct {
|
||||
|
||||
pub fn blob(self: *Response, exec: *const Execution) !js.Promise {
|
||||
const local = exec.js.local.?;
|
||||
if (self.consume(local)) |rejected| return rejected;
|
||||
try self.consume(local);
|
||||
const body = switch (self._body) {
|
||||
.bytes => |b| b,
|
||||
.empty => "",
|
||||
.stream => return local.rejectPromise(.{ .type_error = "Cannot read blob from stream body" }),
|
||||
.stream => return local.typeError("Cannot read blob from stream body"),
|
||||
};
|
||||
const content_type = try self._headers.get("content-type", exec) orelse "";
|
||||
const b = try Blob.initFromBytes(body, content_type, exec);
|
||||
@@ -477,26 +470,26 @@ pub fn blob(self: *Response, exec: *const Execution) !js.Promise {
|
||||
|
||||
pub fn bytes(self: *Response, exec: *const Execution) !js.Promise {
|
||||
const local = exec.js.local.?;
|
||||
if (self.consume(local)) |rejected| return rejected;
|
||||
try self.consume(local);
|
||||
const body = switch (self._body) {
|
||||
.bytes => |b| b,
|
||||
.empty => "",
|
||||
.stream => return local.rejectPromise(.{ .type_error = "Cannot read bytes from stream body" }),
|
||||
.stream => return local.typeError("Cannot read bytes from stream body"),
|
||||
};
|
||||
return local.resolvePromise(js.TypedArray(u8){ .values = body });
|
||||
}
|
||||
|
||||
pub fn formData(self: *Response, exec: *const Execution) !js.Promise {
|
||||
const local = exec.js.local.?;
|
||||
if (self.consume(local)) |rejected| return rejected;
|
||||
try self.consume(local);
|
||||
const body = switch (self._body) {
|
||||
.bytes => |b| b,
|
||||
.empty => "",
|
||||
.stream => return local.rejectPromise(.{ .type_error = "Cannot read FormData from stream body" }),
|
||||
.stream => return local.typeError("Cannot read FormData from stream body"),
|
||||
};
|
||||
|
||||
const content_type = try self._headers.get("content-type", exec) orelse {
|
||||
return local.rejectPromise(.{ .type_error = "Failed to parse body as FormData" });
|
||||
return local.typeError("Failed to parse body as FormData");
|
||||
};
|
||||
var it = ContentTypeIterator.init(content_type);
|
||||
const essence = it.essence;
|
||||
@@ -507,12 +500,12 @@ pub fn formData(self: *Response, exec: *const Execution) !js.Promise {
|
||||
if (std.ascii.eqlIgnoreCase(essence, "multipart/form-data")) {
|
||||
const boundary = it.findBoundary();
|
||||
if (boundary.len == 0) {
|
||||
return local.rejectPromise(.{ .type_error = "Failed to parse body as FormData" });
|
||||
return local.typeError("Failed to parse body as FormData");
|
||||
}
|
||||
|
||||
const form_data = FormData.initFromMultipart(body, boundary, exec) catch |err| switch (err) {
|
||||
error.OutOfMemory => return err,
|
||||
else => return local.rejectPromise(.{ .type_error = "Failed to parse body as FormData" }),
|
||||
else => return local.typeError("Failed to parse body as FormData"),
|
||||
};
|
||||
return local.resolvePromise(form_data);
|
||||
}
|
||||
@@ -520,12 +513,12 @@ pub fn formData(self: *Response, exec: *const Execution) !js.Promise {
|
||||
if (std.ascii.eqlIgnoreCase(essence, "application/x-www-form-urlencoded")) {
|
||||
const form_data = FormData.initFromUrlEncoded(body, exec) catch |err| switch (err) {
|
||||
error.OutOfMemory => return err,
|
||||
else => return local.rejectPromise(.{ .type_error = "Failed to parse body as FormData" }),
|
||||
else => return local.typeError("Failed to parse body as FormData"),
|
||||
};
|
||||
return local.resolvePromise(form_data);
|
||||
}
|
||||
|
||||
return local.rejectPromise(.{ .type_error = "Failed to parse body as FormData" });
|
||||
return local.typeError("Failed to parse body as FormData");
|
||||
}
|
||||
|
||||
pub fn clone(self: *const Response, exec: *const Execution) !*Response {
|
||||
|
||||
@@ -389,7 +389,7 @@ const DeleteContext = struct {
|
||||
pub fn databases(_: *IDBFactory, exec: *Execution) !js.Promise {
|
||||
const local = exec.js.local.?;
|
||||
// unavailable for opaque origins, e.g. about:blank
|
||||
const origin = exec.origin() orelse return local.rejectPromise(.{ .dom_exception = .{ .err = error.SecurityError } });
|
||||
const origin = exec.origin() orelse return error.SecurityError;
|
||||
const engine = try exec.session.idb.engineForOrigin(origin);
|
||||
return local.resolvePromise(try engine.databases(exec.call_arena));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user