Skip to content

Commit 1b2ad80

Browse files
committed
Adds checks for URL schemes
1 parent 72ed592 commit 1b2ad80

File tree

4 files changed

+128
-1
lines changed

4 files changed

+128
-1
lines changed

lib/internal/modules/package_map.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const {
44
JSONParse,
55
ObjectEntries,
6+
RegExpPrototypeExec,
67
SafeMap,
78
StringPrototypeIndexOf,
89
StringPrototypeSlice,
@@ -11,6 +12,7 @@ const {
1112
const assert = require('internal/assert');
1213
const { getLazy } = require('internal/util');
1314
const { resolve: pathResolve, dirname } = require('path');
15+
const { fileURLToPath } = require('internal/url');
1416

1517
const getPackageMapPath = getLazy(() =>
1618
require('internal/options').getOptionValue('--experimental-package-map'),
@@ -23,6 +25,8 @@ const {
2325
ERR_PACKAGE_MAP_KEY_NOT_FOUND,
2426
} = require('internal/errors').codes;
2527

28+
const PROTOCOL_REGEXP = /^[a-zA-Z][a-zA-Z0-9+\-.]*:\/\//;
29+
2630
// Singleton - initialized once on first call
2731
let packageMap;
2832
let packageMapPath;
@@ -73,7 +77,21 @@ class PackageMap {
7377
throw new ERR_PACKAGE_MAP_INVALID(this.#configPath, `package "${key}" is missing "path" field`);
7478
}
7579

76-
const absolutePath = pathResolve(this.#basePath, entry.path);
80+
// Check for URL schemes in path
81+
const urlSchemeMatch = RegExpPrototypeExec(PROTOCOL_REGEXP, entry.path);
82+
let resolvedPath = entry.path;
83+
if (urlSchemeMatch !== null) {
84+
if (urlSchemeMatch[0] === 'file://') {
85+
resolvedPath = fileURLToPath(entry.path);
86+
} else {
87+
throw new ERR_PACKAGE_MAP_INVALID(
88+
this.#configPath,
89+
`package "${key}" has unsupported URL scheme in path "${entry.path}"; only file:// URLs and plain paths are currently supported`,
90+
);
91+
}
92+
}
93+
94+
const absolutePath = pathResolve(this.#basePath, resolvedPath);
7795

7896
this.#packages.set(key, {
7997
path: absolutePath,

test/es-module/test-esm-package-map.mjs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,31 @@
11
import { spawnPromisified } from '../common/index.mjs';
22
import * as fixtures from '../common/fixtures.mjs';
3+
import tmpdir from '../common/tmpdir.js';
34
import assert from 'node:assert';
5+
import { writeFileSync } from 'node:fs';
46
import { execPath } from 'node:process';
57
import { describe, it } from 'node:test';
8+
import { pathToFileURL } from 'node:url';
9+
10+
tmpdir.refresh();
611

712
const packageMapPath = fixtures.path('package-map/package-map.json');
813

14+
// Generated at runtime because file:// URLs must be absolute, making them machine-dependent.
15+
const fileUrlFixturePath = tmpdir.resolve('package-map-file-url.json');
16+
writeFileSync(fileUrlFixturePath, JSON.stringify({
17+
packages: {
18+
root: {
19+
path: pathToFileURL(fixtures.path('package-map/root')).href,
20+
dependencies: { 'dep-a': 'dep-a' },
21+
},
22+
'dep-a': {
23+
path: pathToFileURL(fixtures.path('package-map/dep-a')).href,
24+
dependencies: {},
25+
},
26+
},
27+
}));
28+
929
describe('ESM: --experimental-package-map', () => {
1030

1131
// =========== Basic Resolution ===========
@@ -130,6 +150,32 @@ describe('ESM: --experimental-package-map', () => {
130150
assert.match(stderr, /not found/);
131151
});
132152

153+
it('throws ERR_PACKAGE_MAP_INVALID for unsupported URL scheme in path', async () => {
154+
const { code, stderr } = await spawnPromisified(execPath, [
155+
'--experimental-package-map',
156+
fixtures.path('package-map/package-map-https-path.json'),
157+
'--input-type=module',
158+
'--eval', `import x from 'dep-a';`,
159+
], { cwd: fixtures.path('package-map/root') });
160+
161+
assert.notStrictEqual(code, 0);
162+
assert.match(stderr, /ERR_PACKAGE_MAP_INVALID/);
163+
assert.match(stderr, /unsupported URL scheme/);
164+
assert.match(stderr, /https:\/\//);
165+
});
166+
167+
it('accepts file:// URLs in path', async () => {
168+
const { code, stdout, stderr } = await spawnPromisified(execPath, [
169+
'--experimental-package-map', fileUrlFixturePath,
170+
'--input-type=module',
171+
'--eval',
172+
`import dep from 'dep-a'; console.log(dep);`,
173+
], { cwd: fixtures.path('package-map/root') });
174+
175+
assert.strictEqual(code, 0, stderr);
176+
assert.match(stdout, /dep-a-value/);
177+
});
178+
133179
it('throws ERR_PACKAGE_MAP_INVALID for duplicate package paths', async () => {
134180
const { code, stderr } = await spawnPromisified(execPath, [
135181
'--experimental-package-map',
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"packages": {
3+
"root": {
4+
"path": "./root",
5+
"dependencies": {"dep-a": "dep-a"}
6+
},
7+
"dep-a": {
8+
"path": "https://example.com/dep-a",
9+
"dependencies": {}
10+
}
11+
}
12+
}

test/parallel/test-require-package-map.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,30 @@ require('../common');
44
const fixtures = require('../common/fixtures');
55
const assert = require('node:assert');
66
const { spawnSync } = require('node:child_process');
7+
const { writeFileSync } = require('node:fs');
78
const { describe, it } = require('node:test');
9+
const { pathToFileURL } = require('node:url');
10+
const tmpdir = require('../common/tmpdir');
11+
12+
tmpdir.refresh();
813

914
const packageMapPath = fixtures.path('package-map/package-map.json');
1015

16+
// Generated at runtime because file:// URLs must be absolute, making them machine-dependent.
17+
const fileUrlFixturePath = tmpdir.resolve('package-map-file-url.json');
18+
writeFileSync(fileUrlFixturePath, JSON.stringify({
19+
packages: {
20+
root: {
21+
path: pathToFileURL(fixtures.path('package-map/root')).href,
22+
dependencies: { 'dep-a': 'dep-a' },
23+
},
24+
'dep-a': {
25+
path: pathToFileURL(fixtures.path('package-map/dep-a')).href,
26+
dependencies: {},
27+
},
28+
},
29+
}));
30+
1131
describe('CJS: --experimental-package-map', () => {
1232

1333
describe('basic resolution', () => {
@@ -115,6 +135,37 @@ describe('CJS: --experimental-package-map', () => {
115135
assert.match(stderr, /ERR_PACKAGE_MAP_INVALID/);
116136
});
117137

138+
it('throws for unsupported URL scheme in path', () => {
139+
const { status, stderr } = spawnSync(process.execPath, [
140+
'--experimental-package-map',
141+
fixtures.path('package-map/package-map-https-path.json'),
142+
'-e',
143+
`require('dep-a');`,
144+
], {
145+
cwd: fixtures.path('package-map/root'),
146+
encoding: 'utf8',
147+
});
148+
149+
assert.notStrictEqual(status, 0);
150+
assert.match(stderr, /ERR_PACKAGE_MAP_INVALID/);
151+
assert.match(stderr, /unsupported URL scheme/);
152+
assert.match(stderr, /https:\/\//);
153+
});
154+
155+
it('accepts file:// URLs in path', () => {
156+
const { status, stdout, stderr } = spawnSync(process.execPath, [
157+
'--experimental-package-map', fileUrlFixturePath,
158+
'-e',
159+
`const dep = require('dep-a'); console.log(dep.default);`,
160+
], {
161+
cwd: fixtures.path('package-map/root'),
162+
encoding: 'utf8',
163+
});
164+
165+
assert.strictEqual(status, 0, stderr);
166+
assert.match(stdout, /dep-a-value/);
167+
});
168+
118169
it('throws for duplicate package paths', () => {
119170
const { status, stderr } = spawnSync(process.execPath, [
120171
'--experimental-package-map',

0 commit comments

Comments
 (0)