Skip to content

Commit 5000736

Browse files
committed
path: update win32 toNamespacedPath to support device namespace paths
1 parent 880c446 commit 5000736

File tree

2 files changed

+59
-0
lines changed

2 files changed

+59
-0
lines changed

lib/path.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const {
2525
ArrayPrototypeJoin,
2626
ArrayPrototypeSlice,
2727
FunctionPrototypeBind,
28+
ObjectValues,
2829
StringPrototypeCharCodeAt,
2930
StringPrototypeIndexOf,
3031
StringPrototypeLastIndexOf,
@@ -180,6 +181,25 @@ function glob(path, pattern, windows) {
180181
});
181182
}
182183

184+
// Regular expressions to identify special device names in Windows.
185+
// Ref: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
186+
// COM to AUX (e.g., COM1, LPT1, NUL, CON, PRN, AUX) are reserved OS device names.
187+
// Paths like C:\path\to\COM1 map to \\.\COM1, referencing hardware or system streams.
188+
// Ref: https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#maximum-path-length-limitation
189+
const windowDevicePatterns = {
190+
comPortRegex: /([\\/])?(COM\d+)$/i,
191+
lptPortRegex: /([\\/])?(LPT\d+)$/i,
192+
nulDeviceRegex: /([\\/])?(NUL)$/i,
193+
conDeviceRegex: /([\\/])?(CON)$/i,
194+
prnDeviceRegex: /([\\/])?(PRN)$/i,
195+
auxDeviceRegex: /([\\/])?(AUX)$/i,
196+
physicalDriveRegex: /^(PHYSICALDRIVE\d+)$/i,
197+
pipeRegex: /^(PIPE\\.+)$/i,
198+
mailslotRegex: /^(MAILSLOT\\.+)$/i,
199+
tapeDriveRegex: /^(TAPE\d+)$/i,
200+
changerDeviceRegex: /^(CHANGER\d+)$/i,
201+
};
202+
183203
const win32 = {
184204
/**
185205
* path.resolve([from ...], to)
@@ -687,6 +707,19 @@ const win32 = {
687707
if (typeof path !== 'string' || path.length === 0)
688708
return path;
689709

710+
// Check if the path matches any device pattern
711+
if (ObjectValues(windowDevicePatterns).some((pattern) => pattern.test(path))) {
712+
let deviceName;
713+
if (windowDevicePatterns.pipeRegex.test(path) || windowDevicePatterns.mailslotRegex.test(path)) {
714+
// If the path starts with PIPE\ or MAILSLOT\, keep it as is
715+
deviceName = path;
716+
} else {
717+
// Extract the last component after the last slash or backslash
718+
deviceName = path.split(/[\\/]/).pop();
719+
}
720+
return `\\\\.\\${deviceName}`;
721+
}
722+
690723
const resolvedPath = win32.resolve(path);
691724

692725
if (resolvedPath.length <= 2)

test/parallel/test-path-makelong.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,34 @@ if (common.isWindows) {
3939
assert.strictEqual(path.toNamespacedPath(
4040
'\\\\?\\UNC\\someserver\\someshare\\somefile'),
4141
'\\\\?\\UNC\\someserver\\someshare\\somefile');
42+
// Device name tests
43+
assert.strictEqual(path.toNamespacedPath('C:\\path\\COM1'),
44+
'\\\\.\\COM1');
45+
assert.strictEqual(path.toNamespacedPath('COM1'),
46+
'\\\\.\\COM1');
47+
assert.strictEqual(path.toNamespacedPath('LPT1'),
48+
'\\\\.\\LPT1');
49+
assert.strictEqual(path.toNamespacedPath('C:\\LPT1'),
50+
'\\\\.\\LPT1');
51+
assert.strictEqual(path.toNamespacedPath('PhysicalDrive0'),
52+
'\\\\.\\PhysicalDrive0');
53+
assert.strictEqual(path.toNamespacedPath('pipe\\mypipe'),
54+
'\\\\.\\pipe\\mypipe');
55+
assert.strictEqual(path.toNamespacedPath('MAILSLOT\\mySlot'),
56+
'\\\\.\\MAILSLOT\\mySlot');
57+
assert.strictEqual(path.toNamespacedPath('NUL'),
58+
'\\\\.\\NUL');
59+
assert.strictEqual(path.toNamespacedPath('Tape0'),
60+
'\\\\.\\Tape0');
61+
assert.strictEqual(path.toNamespacedPath('Changer0'),
62+
'\\\\.\\Changer0');
63+
// Test cases for inputs with "\\.\" prefix
4264
assert.strictEqual(path.toNamespacedPath('\\\\.\\pipe\\somepipe'),
4365
'\\\\.\\pipe\\somepipe');
66+
assert.strictEqual(path.toNamespacedPath('\\\\.\\COM1'),
67+
'\\\\.\\COM1');
68+
assert.strictEqual(path.toNamespacedPath('\\\\.\\LPT1'),
69+
'\\\\.\\LPT1');
4470
}
4571

4672
assert.strictEqual(path.toNamespacedPath(''), '');

0 commit comments

Comments
 (0)