From a635d2b558afcaa1e3fdd9a8745e0d2b00422bbd Mon Sep 17 00:00:00 2001 From: shakilurrehman21 Date: Sun, 1 Nov 2026 11:50:47 +0530 Subject: [PATCH] url: fix case-insensitive extended UNC paths in pathToFileURL Windows paths and namespaces are case-insensitive. Extended UNC paths prefixed with `\\?\unc\` or mixed case like `\\?\Unc\` were not recognized by `pathToFileURL` because it only checked for uppercase `\\?\UNC\`. As a result, the UNC prefix was incorrectly handled as a standard UNC path with an empty hostname, causing `fileURLToPath` to throw ERR_INVALID_FILE_URL_PATH on the resulting URL. Additionally, extended UNC paths with empty servernames (such as `\\?\UNC\\share`) were bypassing validation because the empty servername check was hardcoded to `hostnameEndIndex === 2` instead of checking against `prefixLength`. This commit updates `pathToFileURL` to: - Match `\\?\UNC\` case-insensitively using `StringPrototypeToUpperCase`. - Validate that `hostnameEndIndex !== prefixLength` before slicing the hostname, properly throwing ERR_INVALID_ARG_VALUE when the extended UNC servername is empty. - Add test coverage for lowercase and mixed-case extended UNC paths and empty extended UNC servernames. Signed-off-by: shakilurrehman21 --- lib/internal/url.js | 6 ++++-- test/parallel/test-url-pathtofileurl.js | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/lib/internal/url.js b/lib/internal/url.js index 8c04ac70c07c..f089ff3439c9 100644 --- a/lib/internal/url.js +++ b/lib/internal/url.js @@ -23,6 +23,7 @@ const { StringPrototypeIndexOf, StringPrototypeSlice, StringPrototypeStartsWith, + StringPrototypeToUpperCase, StringPrototypeToWellFormed, Symbol, SymbolIterator, @@ -1685,7 +1686,8 @@ function pathToFileURL(filepath, options = kEmptyObject) { // Handle extended UNC path and standard UNC path // "\\?\UNC\" path prefix should be ignored. // Ref: https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation - const isExtendedUNC = StringPrototypeStartsWith(resolved, '\\\\?\\UNC\\'); + const isExtendedUNC = StringPrototypeStartsWith(resolved, '\\\\?\\') && + StringPrototypeToUpperCase(StringPrototypeSlice(resolved, 4, 8)) === 'UNC\\'; const prefixLength = isExtendedUNC ? 8 : 2; const hostnameEndIndex = StringPrototypeIndexOf(resolved, '\\', prefixLength); if (hostnameEndIndex === -1) { @@ -1695,7 +1697,7 @@ function pathToFileURL(filepath, options = kEmptyObject) { 'Missing UNC resource path', ); } - if (hostnameEndIndex === 2) { + if (hostnameEndIndex === prefixLength) { throw new ERR_INVALID_ARG_VALUE( 'path', resolved, diff --git a/test/parallel/test-url-pathtofileurl.js b/test/parallel/test-url-pathtofileurl.js index 290cac41037c..816bf7377eca 100644 --- a/test/parallel/test-url-pathtofileurl.js +++ b/test/parallel/test-url-pathtofileurl.js @@ -95,6 +95,22 @@ const url = require('url'); code: 'ERR_INVALID_ARG_VALUE', }); + // Missing server in extended UNC: + assert.throws(() => url.pathToFileURL('\\\\?\\UNC\\\\no-server'), { + code: 'ERR_INVALID_ARG_VALUE', + }); + assert.throws(() => url.pathToFileURL('\\\\?\\unc\\\\no-server'), { + code: 'ERR_INVALID_ARG_VALUE', + }); + + // Missing share or resource in extended UNC: + assert.throws(() => url.pathToFileURL('\\\\?\\UNC\\host'), { + code: 'ERR_INVALID_ARG_VALUE', + }); + assert.throws(() => url.pathToFileURL('\\\\?\\unc\\host'), { + code: 'ERR_INVALID_ARG_VALUE', + }); + // Regression test for direct String.prototype.startsWith call assert.throws(() => url.pathToFileURL([ '\\\\', @@ -173,6 +189,10 @@ const windowsTestCases = [ { path: '\\\\nas\\My Docs\\File.doc', expected: 'file://nas/My%20Docs/File.doc' }, // Extended UNC path { path: '\\\\?\\UNC\\server\\share\\folder\\file.txt', expected: 'file://server/share/folder/file.txt' }, + // Lowercase extended UNC path + { path: '\\\\?\\unc\\server\\share\\folder\\file.txt', expected: 'file://server/share/folder/file.txt' }, + // Mixed-case extended UNC path + { path: '\\\\?\\Unc\\server\\share\\folder\\file.txt', expected: 'file://server/share/folder/file.txt' }, ]; const alphabet = String.fromCharCode(...Array.from({ length: 26 }, (_, i) => 'a'.charCodeAt() + i)); const posixTestCases = [