Skip to content

Commit 3fbe157

Browse files
authored
esm: improve error when calling import.meta.resolve from data: URL
PR-URL: #49516 Reviewed-By: James M Snell <[email protected]>
1 parent 39d3f42 commit 3fbe157

4 files changed

Lines changed: 75 additions & 11 deletions

File tree

doc/api/errors.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2959,6 +2959,26 @@ import 'package-name'; // supported
29592959

29602960
`import` with URL schemes other than `file` and `data` is unsupported.
29612961

2962+
<a id="ERR_UNSUPPORTED_RESOLVE_REQUEST"></a>
2963+
2964+
### `ERR_UNSUPPORTED_RESOLVE_REQUEST`
2965+
2966+
An attempt was made to resolve an invalid module referrer. This can happen when
2967+
importing or calling `import.meta.resolve()` with either:
2968+
2969+
* a bare specifier that is not a builtin module from a module whose URL scheme
2970+
is not `file`.
2971+
* a [relative URL][] from a module whose URL scheme is not a [special scheme][].
2972+
2973+
```mjs
2974+
try {
2975+
// Trying to import the package 'bare-specifier' from a `data:` URL module:
2976+
await import('data:text/javascript,import "bare-specifier"');
2977+
} catch (e) {
2978+
console.log(e.code); // ERR_UNSUPPORTED_RESOLVE_REQUEST
2979+
}
2980+
```
2981+
29622982
<a id="ERR_USE_AFTER_CLOSE"></a>
29632983

29642984
### `ERR_USE_AFTER_CLOSE`
@@ -3710,7 +3730,9 @@ The native call from `process.cpuUsage` could not be processed.
37103730
[event emitter-based]: events.md#class-eventemitter
37113731
[file descriptors]: https://en.wikipedia.org/wiki/File_descriptor
37123732
[policy]: permissions.md#policies
3733+
[relative URL]: https://url.spec.whatwg.org/#relative-url-string
37133734
[self-reference a package using its name]: packages.md#self-referencing-a-package-using-its-name
3735+
[special scheme]: https://url.spec.whatwg.org/#special-scheme
37143736
[stream-based]: stream.md
37153737
[syscall]: https://man7.org/linux/man-pages/man2/syscalls.2.html
37163738
[try-catch]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch

lib/internal/errors.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1863,6 +1863,9 @@ E('ERR_UNSUPPORTED_ESM_URL_SCHEME', (url, supported) => {
18631863
msg += `. Received protocol '${url.protocol}'`;
18641864
return msg;
18651865
}, Error);
1866+
E('ERR_UNSUPPORTED_RESOLVE_REQUEST',
1867+
'Failed to resolve module specifier "%s" from "%s": Invalid relative URL or base scheme is not hierarchical.',
1868+
TypeError);
18661869
E('ERR_USE_AFTER_CLOSE', '%s was closed', Error);
18671870

18681871
// This should probably be a `TypeError`.

lib/internal/modules/esm/resolve.js

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const experimentalNetworkImports =
3737
getOptionValue('--experimental-network-imports');
3838
const inputTypeFlag = getOptionValue('--input-type');
3939
const { URL, pathToFileURL, fileURLToPath, isURL } = require('internal/url');
40-
const { getCWDURL } = require('internal/util');
40+
const { getCWDURL, setOwnProperty } = require('internal/util');
4141
const { canParse: URLCanParse } = internalBinding('url');
4242
const { legacyMainResolve: FSLegacyMainResolve } = internalBinding('fs');
4343
const {
@@ -51,6 +51,7 @@ const {
5151
ERR_PACKAGE_IMPORT_NOT_DEFINED,
5252
ERR_PACKAGE_PATH_NOT_EXPORTED,
5353
ERR_UNSUPPORTED_DIR_IMPORT,
54+
ERR_UNSUPPORTED_RESOLVE_REQUEST,
5455
ERR_NETWORK_IMPORT_DISALLOWED,
5556
} = require('internal/errors').codes;
5657

@@ -884,22 +885,37 @@ function shouldBeTreatedAsRelativeOrAbsolutePath(specifier) {
884885
* @param {boolean} preserveSymlinks - Whether to preserve symlinks in the resolved URL.
885886
*/
886887
function moduleResolve(specifier, base, conditions, preserveSymlinks) {
887-
const isRemote = base.protocol === 'http:' ||
888-
base.protocol === 'https:';
888+
const protocol = typeof base === 'string' ?
889+
StringPrototypeSlice(base, 0, StringPrototypeIndexOf(base, ':') + 1) :
890+
base.protocol;
891+
const isData = protocol === 'data:';
892+
const isRemote =
893+
isData ||
894+
protocol === 'http:' ||
895+
protocol === 'https:';
889896
// Order swapped from spec for minor perf gain.
890897
// Ok since relative URLs cannot parse as URLs.
891898
let resolved;
892899
if (shouldBeTreatedAsRelativeOrAbsolutePath(specifier)) {
893-
resolved = new URL(specifier, base);
894-
} else if (!isRemote && specifier[0] === '#') {
900+
try {
901+
resolved = new URL(specifier, base);
902+
} catch (cause) {
903+
const error = new ERR_UNSUPPORTED_RESOLVE_REQUEST(specifier, base);
904+
setOwnProperty(error, 'cause', cause);
905+
throw error;
906+
}
907+
} else if (protocol === 'file:' && specifier[0] === '#') {
895908
resolved = packageImportsResolve(specifier, base, conditions);
896909
} else {
897910
try {
898911
resolved = new URL(specifier);
899-
} catch {
900-
if (!isRemote) {
901-
resolved = packageResolve(specifier, base, conditions);
912+
} catch (cause) {
913+
if (isRemote && !BuiltinModule.canBeRequiredWithoutScheme(specifier)) {
914+
const error = new ERR_UNSUPPORTED_RESOLVE_REQUEST(specifier, base);
915+
setOwnProperty(error, 'cause', cause);
916+
throw error;
902917
}
918+
resolved = packageResolve(specifier, base, conditions);
903919
}
904920
}
905921
if (resolved.protocol !== 'file:') {
@@ -1073,7 +1089,7 @@ function defaultResolve(specifier, context = {}) {
10731089
}
10741090
}
10751091

1076-
let parsed;
1092+
let parsed, protocol;
10771093
try {
10781094
if (shouldBeTreatedAsRelativeOrAbsolutePath(specifier)) {
10791095
parsed = new URL(specifier, parsedParentURL);
@@ -1082,7 +1098,7 @@ function defaultResolve(specifier, context = {}) {
10821098
}
10831099

10841100
// Avoid accessing the `protocol` property due to the lazy getters.
1085-
const protocol = parsed.protocol;
1101+
protocol = parsed.protocol;
10861102
if (protocol === 'data:' ||
10871103
(experimentalNetworkImports &&
10881104
(
@@ -1109,7 +1125,8 @@ function defaultResolve(specifier, context = {}) {
11091125
if (maybeReturn) { return maybeReturn; }
11101126

11111127
// This must come after checkIfDisallowedImport
1112-
if (parsed && parsed.protocol === 'node:') { return { __proto__: null, url: specifier }; }
1128+
protocol ??= parsed?.protocol;
1129+
if (protocol === 'node:') { return { __proto__: null, url: specifier }; }
11131130

11141131

11151132
const isMain = parentURL === undefined;

test/es-module/test-esm-import-meta-resolve.mjs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,28 @@ assert.strictEqual(import.meta.resolve('http://some-absolute/url'), 'http://some
3636
assert.strictEqual(import.meta.resolve('some://weird/protocol'), 'some://weird/protocol');
3737
assert.strictEqual(import.meta.resolve('baz/', fixtures),
3838
fixtures + 'node_modules/baz/');
39+
assert.deepStrictEqual(
40+
{ ...await import('data:text/javascript,export default import.meta.resolve("http://some-absolute/url")') },
41+
{ default: 'http://some-absolute/url' },
42+
);
43+
assert.deepStrictEqual(
44+
{ ...await import('data:text/javascript,export default import.meta.resolve("some://weird/protocol")') },
45+
{ default: 'some://weird/protocol' },
46+
);
47+
assert.deepStrictEqual(
48+
{ ...await import(`data:text/javascript,export default import.meta.resolve("baz/", ${JSON.stringify(fixtures)})`) },
49+
{ default: fixtures + 'node_modules/baz/' },
50+
);
51+
assert.deepStrictEqual(
52+
{ ...await import('data:text/javascript,export default import.meta.resolve("fs")') },
53+
{ default: 'node:fs' },
54+
);
55+
await assert.rejects(import('data:text/javascript,export default import.meta.resolve("does-not-exist")'), {
56+
code: 'ERR_UNSUPPORTED_RESOLVE_REQUEST',
57+
});
58+
await assert.rejects(import('data:text/javascript,export default import.meta.resolve("./relative")'), {
59+
code: 'ERR_UNSUPPORTED_RESOLVE_REQUEST',
60+
});
3961

4062
{
4163
const cp = spawn(execPath, [

0 commit comments

Comments
 (0)