From d2d03cfe0ea3c160dbe1bc4d7e87eadfdff84088 Mon Sep 17 00:00:00 2001 From: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> Date: Wed, 19 Jan 2022 06:28:32 +0100 Subject: [PATCH] esm: improve validation of resolved URLs PR-URL: https://github.com/nodejs/node/pull/41446 Reviewed-By: Bradley Farias Reviewed-By: Geoffrey Booth Reviewed-By: Guy Bedford --- lib/internal/modules/esm/loader.js | 7 +++++-- test/es-module/test-esm-loader-invalid-url.mjs | 6 +----- test/fixtures/es-module-loaders/hooks-custom.mjs | 15 ++++++++------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index 91f570297be341..1707db8b1857fa 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -29,7 +29,7 @@ const { ERR_INVALID_RETURN_VALUE, ERR_UNKNOWN_MODULE_FORMAT } = require('internal/errors').codes; -const { pathToFileURL, isURLInstance } = require('internal/url'); +const { pathToFileURL, isURLInstance, URL } = require('internal/url'); const { isAnyArrayBuffer, isArrayBufferView, @@ -558,7 +558,8 @@ class ESMLoader { format, ); } - if (typeof url !== 'string') { + + if (typeof url !== 'string') { // non-strings can be coerced to a url string throw new ERR_INVALID_RETURN_PROPERTY_VALUE( 'string', 'loader resolve', @@ -567,6 +568,8 @@ class ESMLoader { ); } + new URL(url); // Intentionally trigger error if `url` is invalid + return { format, url, diff --git a/test/es-module/test-esm-loader-invalid-url.mjs b/test/es-module/test-esm-loader-invalid-url.mjs index 7dce946da2c3c3..6294e57404c8bb 100644 --- a/test/es-module/test-esm-loader-invalid-url.mjs +++ b/test/es-module/test-esm-loader-invalid-url.mjs @@ -4,11 +4,7 @@ import assert from 'assert'; import('../fixtures/es-modules/test-esm-ok.mjs') .then(assert.fail, (error) => { - expectsError({ - code: 'ERR_INVALID_URL', - message: 'Invalid URL' - })(error); - + expectsError({ code: 'ERR_INVALID_URL' })(error); assert.strictEqual(error.input, '../fixtures/es-modules/test-esm-ok.mjs'); }) .then(mustCall()); diff --git a/test/fixtures/es-module-loaders/hooks-custom.mjs b/test/fixtures/es-module-loaders/hooks-custom.mjs index cd9d5020ad3234..5173b97387905a 100644 --- a/test/fixtures/es-module-loaders/hooks-custom.mjs +++ b/test/fixtures/es-module-loaders/hooks-custom.mjs @@ -1,3 +1,4 @@ +import { pathToFileURL } from 'node:url'; import count from '../es-modules/stateful.mjs'; @@ -24,28 +25,28 @@ export function load(url, context, next) { format: 'module', }); - if (url === 'esmHook/badReturnVal.mjs') return 'export function returnShouldBeObject() {}'; + if (url.endsWith('esmHook/badReturnVal.mjs')) return 'export function returnShouldBeObject() {}'; - if (url === 'esmHook/badReturnFormatVal.mjs') return { + if (url.endsWith('esmHook/badReturnFormatVal.mjs')) return { format: Array(0), source: '', } - if (url === 'esmHook/unsupportedReturnFormatVal.mjs') return { + if (url.endsWith('esmHook/unsupportedReturnFormatVal.mjs')) return { format: 'foo', // Not one of the allowable inputs: no translator named 'foo' source: '', } - if (url === 'esmHook/badReturnSourceVal.mjs') return { + if (url.endsWith('esmHook/badReturnSourceVal.mjs')) return { format: 'module', source: Array(0), } - if (url === 'esmHook/preknownFormat.pre') return { + if (url.endsWith('esmHook/preknownFormat.pre')) return { format: context.format, source: `const msg = 'hello world'; export default msg;` }; - if (url === 'esmHook/virtual.mjs') return { + if (url.endsWith('esmHook/virtual.mjs')) return { format: 'module', source: `export const message = 'Woohoo!'.toUpperCase();`, }; @@ -62,7 +63,7 @@ export function resolve(specifier, context, next) { if (specifier.startsWith('esmHook')) return { format, - url: specifier, + url: pathToFileURL(specifier).href, importAssertions: context.importAssertions, };