From 500073668f9b436987db1e20397193bbc05ab772 Mon Sep 17 00:00:00 2001 From: EarlyRiser42 Date: Wed, 14 Aug 2024 18:17:34 +0900 Subject: [PATCH] path: update win32 toNamespacedPath to support device namespace paths --- lib/path.js | 33 +++++++++++++++++++++++++++++ test/parallel/test-path-makelong.js | 26 +++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/lib/path.js b/lib/path.js index eba07f376ad0f9..0c4144d6205873 100644 --- a/lib/path.js +++ b/lib/path.js @@ -25,6 +25,7 @@ const { ArrayPrototypeJoin, ArrayPrototypeSlice, FunctionPrototypeBind, + ObjectValues, StringPrototypeCharCodeAt, StringPrototypeIndexOf, StringPrototypeLastIndexOf, @@ -180,6 +181,25 @@ function glob(path, pattern, windows) { }); } +// Regular expressions to identify special device names in Windows. +// Ref: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea +// COM to AUX (e.g., COM1, LPT1, NUL, CON, PRN, AUX) are reserved OS device names. +// Paths like C:\path\to\COM1 map to \\.\COM1, referencing hardware or system streams. +// Ref: https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#maximum-path-length-limitation +const windowDevicePatterns = { + comPortRegex: /([\\/])?(COM\d+)$/i, + lptPortRegex: /([\\/])?(LPT\d+)$/i, + nulDeviceRegex: /([\\/])?(NUL)$/i, + conDeviceRegex: /([\\/])?(CON)$/i, + prnDeviceRegex: /([\\/])?(PRN)$/i, + auxDeviceRegex: /([\\/])?(AUX)$/i, + physicalDriveRegex: /^(PHYSICALDRIVE\d+)$/i, + pipeRegex: /^(PIPE\\.+)$/i, + mailslotRegex: /^(MAILSLOT\\.+)$/i, + tapeDriveRegex: /^(TAPE\d+)$/i, + changerDeviceRegex: /^(CHANGER\d+)$/i, +}; + const win32 = { /** * path.resolve([from ...], to) @@ -687,6 +707,19 @@ const win32 = { if (typeof path !== 'string' || path.length === 0) return path; + // Check if the path matches any device pattern + if (ObjectValues(windowDevicePatterns).some((pattern) => pattern.test(path))) { + let deviceName; + if (windowDevicePatterns.pipeRegex.test(path) || windowDevicePatterns.mailslotRegex.test(path)) { + // If the path starts with PIPE\ or MAILSLOT\, keep it as is + deviceName = path; + } else { + // Extract the last component after the last slash or backslash + deviceName = path.split(/[\\/]/).pop(); + } + return `\\\\.\\${deviceName}`; + } + const resolvedPath = win32.resolve(path); if (resolvedPath.length <= 2) diff --git a/test/parallel/test-path-makelong.js b/test/parallel/test-path-makelong.js index 7a4783953c8fde..4486986417d32c 100644 --- a/test/parallel/test-path-makelong.js +++ b/test/parallel/test-path-makelong.js @@ -39,8 +39,34 @@ if (common.isWindows) { assert.strictEqual(path.toNamespacedPath( '\\\\?\\UNC\\someserver\\someshare\\somefile'), '\\\\?\\UNC\\someserver\\someshare\\somefile'); + // Device name tests + assert.strictEqual(path.toNamespacedPath('C:\\path\\COM1'), + '\\\\.\\COM1'); + assert.strictEqual(path.toNamespacedPath('COM1'), + '\\\\.\\COM1'); + assert.strictEqual(path.toNamespacedPath('LPT1'), + '\\\\.\\LPT1'); + assert.strictEqual(path.toNamespacedPath('C:\\LPT1'), + '\\\\.\\LPT1'); + assert.strictEqual(path.toNamespacedPath('PhysicalDrive0'), + '\\\\.\\PhysicalDrive0'); + assert.strictEqual(path.toNamespacedPath('pipe\\mypipe'), + '\\\\.\\pipe\\mypipe'); + assert.strictEqual(path.toNamespacedPath('MAILSLOT\\mySlot'), + '\\\\.\\MAILSLOT\\mySlot'); + assert.strictEqual(path.toNamespacedPath('NUL'), + '\\\\.\\NUL'); + assert.strictEqual(path.toNamespacedPath('Tape0'), + '\\\\.\\Tape0'); + assert.strictEqual(path.toNamespacedPath('Changer0'), + '\\\\.\\Changer0'); + // Test cases for inputs with "\\.\" prefix assert.strictEqual(path.toNamespacedPath('\\\\.\\pipe\\somepipe'), '\\\\.\\pipe\\somepipe'); + assert.strictEqual(path.toNamespacedPath('\\\\.\\COM1'), + '\\\\.\\COM1'); + assert.strictEqual(path.toNamespacedPath('\\\\.\\LPT1'), + '\\\\.\\LPT1'); } assert.strictEqual(path.toNamespacedPath(''), '');