Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 17 additions & 14 deletions lib/internal/abort_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@ function refreshCompositeSignal(signal) {
continue;
}

if (sourceSignal.aborted) {
abortSignal(signal, sourceSignal.reason);
if (sourceSignal[kAborted]) {
abortSignal(signal, sourceSignal[kReason]);
return;
}
}
Expand All @@ -170,8 +170,8 @@ function followCompositeSignal(signal) {
continue;
}

if (sourceSignal.aborted) {
abortSignal(signal, sourceSignal.reason);
if (sourceSignal[kAborted]) {
abortSignal(signal, sourceSignal[kReason]);
return;
}

Expand Down Expand Up @@ -217,6 +217,14 @@ function setWeakAbortSignalTimeout(weakRef, delay) {
}

class AbortSignal extends EventTarget {
#brand;

static {
converters.AbortSignal = createInterfaceConverter(
'AbortSignal',
(value) => typeof value === 'object' && value !== null && #brand in value,
);
}

/**
* @param {symbol | undefined} dontThrowSymbol
Expand Down Expand Up @@ -337,8 +345,9 @@ class AbortSignal extends EventTarget {
gcPersistentSignals.add(signal);
}

if (signal.aborted) {
abortSignal(resultSignal, signal.reason);
refreshCompositeSignal(signal);
if (signal[kAborted]) {
abortSignal(resultSignal, signal[kReason]);
return resultSignal;
}

Expand All @@ -348,20 +357,15 @@ class AbortSignal extends EventTarget {
} else if (!signal[kSourceSignals]) {
continue;
} else {
refreshCompositeSignal(signal);
if (signal.aborted) {
abortSignal(resultSignal, signal.reason);
return resultSignal;
}
for (const sourceSignalWeakRef of signal[kSourceSignals]) {
const sourceSignal = sourceSignalWeakRef.deref();
if (!sourceSignal) {
continue;
}
assert(!sourceSignal[kComposite]);

if (sourceSignal.aborted) {
abortSignal(resultSignal, sourceSignal.reason);
if (sourceSignal[kAborted]) {
abortSignal(resultSignal, sourceSignal[kReason]);
return resultSignal;
}

Expand Down Expand Up @@ -466,7 +470,6 @@ class AbortSignal extends EventTarget {
}
}

converters.AbortSignal = createInterfaceConverter('AbortSignal', AbortSignal.prototype);
converters['sequence<AbortSignal>'] = createSequenceConverter(converters.AbortSignal);

function ClonedAbortSignal() {
Expand Down
4 changes: 2 additions & 2 deletions lib/internal/crypto/webidl.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ const {
const {
isUint32,
} = require('internal/validators');
const { CryptoKey } = require('internal/crypto/webcrypto');
const {
getCryptoKeyAlgorithm,
getCryptoKeyType,
isCryptoKey,
} = require('internal/crypto/keys');
const {
bigIntArrayToUnsignedInt,
Expand Down Expand Up @@ -618,7 +618,7 @@ converters.AesCtrParams = createDictionaryConverter(
]);

converters.CryptoKey = createInterfaceConverter(
'CryptoKey', CryptoKey.prototype);
'CryptoKey', isCryptoKey);

converters.EcdhKeyDeriveParams = createDictionaryConverter(
'EcdhKeyDeriveParams', [
Expand Down
7 changes: 3 additions & 4 deletions lib/internal/streams/iter/webidl.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ const {
convertToInt,
createDictionaryConverter,
createEnumConverter,
createInterfaceConverter,
createSequenceConverter,
} = require('internal/webidl');
const { AbortSignal } = require('internal/abort_controller');
// Load AbortSignal to register its Web IDL converter in baseConverters.
require('internal/abort_controller');
const { isUint8Array } = require('internal/util/types');

const converters = { __proto__: null };
Expand Down Expand Up @@ -38,8 +38,7 @@ function allowStreamBufferOptions(options) {
};
}

converters.AbortSignal = createInterfaceConverter(
'AbortSignal', AbortSignal.prototype);
converters.AbortSignal = baseConverters.AbortSignal;
converters.BackpressurePolicy = createEnumConverter('BackpressurePolicy', [
'strict',
'unbounded',
Expand Down
7 changes: 3 additions & 4 deletions lib/internal/webidl.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ const {
NumberMAX_SAFE_INTEGER,
NumberMIN_SAFE_INTEGER,
ObjectPrototypeHasOwnProperty,
ObjectPrototypeIsPrototypeOf,
SafeArrayIterator,
SafeSet,
String,
Expand Down Expand Up @@ -873,13 +872,13 @@ function createSequenceConverter(converter) {
* Creates a converter for a Web IDL interface type.
* @see https://webidl.spec.whatwg.org/#js-interface
* @param {string} name Interface identifier.
* @param {object} prototype Interface prototype object.
* @param {(value: any) => boolean} brandCheck Interface brand predicate.
* @returns {Converter}
*/
function createInterfaceConverter(name, prototype) {
function createInterfaceConverter(name, brandCheck) {
return (V, options = kEmptyObject) => {
// Web IDL interface conversion step 1: return V if it implements I.
if (ObjectPrototypeIsPrototypeOf(prototype, V)) {
if (brandCheck(V)) {
return V;
}
// Step 2: otherwise throw.
Expand Down
48 changes: 45 additions & 3 deletions test/parallel/test-internal-webidl.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Flags: --expose-internals
'use strict';

require('../common');
const common = require('../common');
const assert = require('assert');
const vm = require('vm');
const webidl = require('internal/webidl');
Expand Down Expand Up @@ -511,10 +511,16 @@ assert.throws(() => webidl.requiredArguments(1, 2, opts), {
}

{
class Example {}
class Example {
#brand;

static is(value) {
return typeof value === 'object' && value !== null && #brand in value;
}
}
const converter = webidl.createInterfaceConverter(
'Example',
Example.prototype);
Example.is);
const example = new Example();

assert.strictEqual(converter(example), example);
Expand All @@ -523,6 +529,42 @@ assert.throws(() => webidl.requiredArguments(1, 2, opts), {
code: 'ERR_INVALID_ARG_TYPE',
message: 'Prefix: Context is not of type Example.',
});
assertInvalidArgType(() => converter({ __proto__: Example.prototype }));
assertInvalidArgType(() => converter(new Proxy(example, {})));
Object.setPrototypeOf(example, null);
assert.strictEqual(converter(example), example);
}

{
const signal = AbortSignal.abort('reason');
for (const value of [
Object.create(AbortSignal.prototype, { aborted: { value: false } }),
{ __proto__: signal },
Object.create(AbortSignal.prototype, Object.getOwnPropertyDescriptors(signal)),
new Proxy(signal, {}),
]) {
assertInvalidArgType(() => converters.AbortSignal(value));
assertInvalidArgType(() => AbortSignal.any([value]));
}

Object.setPrototypeOf(signal, null);
assert.strictEqual(converters.AbortSignal(signal), signal);
const composite = AbortSignal.any([signal]);
assert.strictEqual(composite.aborted, true);
assert.strictEqual(composite.reason, 'reason');
}

{
const controller = new AbortController();
Object.defineProperties(controller.signal, {
aborted: { get: common.mustNotCall('Unexpected aborted getter') },
reason: { get: common.mustNotCall('Unexpected reason getter') },
});
const composite = AbortSignal.any([controller.signal]);
assert.strictEqual(composite.aborted, false);
controller.abort('reason');
assert.strictEqual(composite.aborted, true);
assert.strictEqual(composite.reason, 'reason');
}

{
Expand Down
5 changes: 3 additions & 2 deletions test/parallel/test-webcrypto-cryptokey-brand-check.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const { subtle } = globalThis.crypto;
assert.strictEqual(Object.getPrototypeOf(internalProto), CryptoKey.prototype);

const invalidThis = { code: 'ERR_INVALID_THIS', name: 'TypeError' };
const invalidArgType = { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError' };

// Plain object receiver.
Object.entries(getters).forEach(([, getter]) => {
Expand Down Expand Up @@ -94,10 +95,10 @@ const { subtle } = globalThis.crypto;
assert.strictEqual(isCryptoKey(spoofed), false);
await assert.rejects(
subtle.sign('HMAC', spoofed, Buffer.from('payload')),
invalidThis);
invalidArgType);
await assert.rejects(
subtle.exportKey('jwk', spoofed),
invalidThis);
invalidArgType);

// Subvert `instanceof CryptoKey` via Symbol.hasInstance, then
// invoke the native getters on a forged object. The C++ tag
Expand Down
38 changes: 38 additions & 0 deletions test/parallel/test-webcrypto-webidl-brand.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Flags: --expose-internals
'use strict';

const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');

const assert = require('assert');
const { subtle } = globalThis.crypto;
const { CryptoKey } = require('internal/crypto/keys');
const { converters } = require('internal/crypto/webidl');

async function main() {
const bytes = new Uint8Array(16);
const key = await subtle.importKey('raw', bytes, 'AES-GCM', true, ['encrypt']);

for (const value of [
{ __proto__: CryptoKey.prototype },
{ __proto__: key },
Object.create(CryptoKey.prototype, Object.getOwnPropertyDescriptors(key)),
new Proxy(key, {}),
]) {
assert.throws(() => converters.CryptoKey(value), {
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE',
});
await assert.rejects(subtle.exportKey('raw', value), {
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE',
});
}

Object.setPrototypeOf(key, null);
assert.strictEqual(converters.CryptoKey(key), key);
assert.deepStrictEqual(new Uint8Array(await subtle.exportKey('raw', key)), bytes);
}

main().then(common.mustCall());
Loading