Skip to content

Commit e01e1c1

Browse files
committed
path: fix normalization of Windows device paths missing colon
test: add test case for device paths missing colon style: fix linter errors path: fix normalization of Windows device paths missing colon
1 parent 35fed19 commit e01e1c1

File tree

3 files changed

+71
-7
lines changed

3 files changed

+71
-7
lines changed

lib/path.js

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -401,12 +401,27 @@ const win32 = {
401401
// We matched a device root (e.g. \\\\.\\PHYSICALDRIVE0)
402402
device = `\\\\${firstPart}`;
403403
rootEnd = 4;
404-
const colonIndex = StringPrototypeIndexOf(path, ':');
405-
// Special case: handle \\?\COM1: or similar reserved device paths
406-
const possibleDevice = StringPrototypeSlice(path, 4, colonIndex + 1);
407-
if (isWindowsReservedName(possibleDevice, possibleDevice.length - 1)) {
408-
device = `\\\\?\\${possibleDevice}`;
409-
rootEnd = 4 + possibleDevice.length;
404+
// Determine the end of the root part (the first slash or end of string)
405+
let rootPartEnd = 4;
406+
while (rootPartEnd < len && !isPathSeparator(StringPrototypeCharCodeAt(path, rootPartEnd))) {
407+
rootPartEnd++;
408+
}
409+
410+
const rootPart = StringPrototypeSlice(path, 4, rootPartEnd);
411+
const colonIndexInRoot = StringPrototypeIndexOf(rootPart, ':');
412+
413+
if (colonIndexInRoot !== -1) {
414+
// Handle \\?\COM1: or similar reserved device paths with colon
415+
if (isWindowsReservedName(rootPart, colonIndexInRoot)) {
416+
device = `\\\\${firstPart}\\${rootPart}`;
417+
rootEnd = 4 + rootPart.length;
418+
}
419+
} else {
420+
// Handle \\.\CON or \\?\CON where the colon is missing
421+
if (isWindowsReservedName(rootPart, rootPart.length)) {
422+
device = `\\\\${firstPart}\\${rootPart}`;
423+
rootEnd = 4 + rootPart.length;
424+
}
410425
}
411426
} else if (j === len) {
412427
// We matched a UNC root only
@@ -471,7 +486,8 @@ const win32 = {
471486
} while ((index = StringPrototypeIndexOf(path, ':', index + 1)) !== -1);
472487
}
473488
const colonIndex = StringPrototypeIndexOf(path, ':');
474-
if (isWindowsReservedName(path, colonIndex)) {
489+
// Ensure colonIndex is valid before calling isWindowsReservedName
490+
if (colonIndex !== -1 && isWindowsReservedName(path, colonIndex)) {
475491
return `.\\${device ?? ''}${tail}`;
476492
}
477493
if (device === undefined) {
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const path = require('path');
5+
6+
// هذا السطر يمنع تشغيل الاختبار على غير ويندوز لأنه خاص بويندوز فقط
7+
if (!common.isWindows)
8+
common.skip('this test is for win32 only');
9+
10+
// Test cases for reserved device names missing the trailing colon
11+
// See: https://github.com/nodejs/node/pull/[YOUR_PR_NUMBER] (optional)
12+
13+
// 1. Check \\.\CON (Missing colon)
14+
assert.strictEqual(path.win32.normalize('\\\\.\\CON'), '\\\\.\\CON');
15+
assert.strictEqual(path.win32.normalize('\\\\.\\con'), '\\\\.\\con'); // Case insensitive
16+
17+
// 2. Check \\?\CON (Missing colon)
18+
assert.strictEqual(path.win32.normalize('\\\\?\\CON'), '\\\\?\\CON');
19+
assert.strictEqual(path.win32.normalize('\\\\?\\con'), '\\\\?\\con');
20+
21+
// 3. Check that regular files are NOT affected (Sanity check)
22+
assert.strictEqual(path.win32.normalize('\\\\.\\PhysicalDrive0'), '\\\\.\\PhysicalDrive0');
23+
24+
// 4. Check join behavior (to ensure it acts as a root)
25+
// If it's a root, joining '..' should not strip the device.
26+
const joined = path.win32.join('\\\\.\\CON', '..');
27+
assert.strictEqual(joined, '\\\\.\\CON');

test/parallel/test-path-win32-normalize-device-names.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,27 @@ for (const { input, expected } of normalizeDeviceNameTests) {
115115
`path.win32.normalize(${JSON.stringify(input)}) === ${JSON.stringify(expected)}, but got ${JSON.stringify(actual)}`);
116116
}
117117

118+
const normalizeReservedDeviceRootTests = [
119+
{ input: '\\\\.\\CON', expected: '\\\\.\\CON' },
120+
{ input: '\\\\?\\PRN', expected: '\\\\?\\PRN' },
121+
{ input: '\\\\.\\AUX\\file.txt', expected: '\\\\.\\AUX\\file.txt' },
122+
{ input: '\\\\?\\COM1/folder/file', expected: '\\\\?\\COM1\\folder\\file' },
123+
{ input: '\\\\.\\CON\\file:ADS', expected: '\\\\.\\CON\\file:ADS' },
124+
{ input: '\\\\?\\PRN\\data:stream', expected: '\\\\?\\PRN\\data:stream' },
125+
{ input: '\\\\.\\CON-prefix', expected: '\\\\.\\CON-prefix' },
126+
{ input: '\\\\?\\PRN-suffix', expected: '\\\\?\\PRN-suffix' },
127+
{ input: '\\\\.\\NOT_A_DEVICE', expected: '\\\\.\\NOT_A_DEVICE' },
128+
];
129+
130+
for (const { input, expected } of normalizeReservedDeviceRootTests) {
131+
const actual = path.win32.normalize(input);
132+
assert.strictEqual(
133+
actual,
134+
expected,
135+
`path.win32.normalize(${JSON.stringify(input)}) === ${JSON.stringify(expected)}, but got ${JSON.stringify(actual)}`
136+
);
137+
}
138+
118139
assert.strictEqual(path.win32.normalize('CON:foo/../bar'), '.\\CON:bar');
119140

120141
// This should NOT be prefixed because 'c:' is treated as a drive letter.

0 commit comments

Comments
 (0)