mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-09-22 12:35:22 -04:00
Merge pull request #3403 from lightpanda-io/error-bridge-rejection
chore: leverage bridge for error rejection
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);
|
||||
|
||||
@@ -257,24 +257,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);
|
||||
@@ -286,17 +283,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" });
|
||||
@@ -306,32 +299,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;
|
||||
@@ -342,12 +329,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);
|
||||
}
|
||||
@@ -355,12 +342,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 {
|
||||
|
||||
@@ -392,7 +392,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