From 20ffdee152040f3f52ac464b47348f743dc8ddc0 Mon Sep 17 00:00:00 2001 From: Berial <715434384@qq.com> Date: Thu, 23 Jul 2026 14:24:19 +0800 Subject: [PATCH 1/3] [path] Strip Windows Extended-Length Path prefixes in absolutePathToUri Aligns `WindowsStyle.absolutePathToUri` (and therefore `toUri()`) with `Uri.file()` / `Uri.directory()` from `dart:io` by stripping Win32 Extended-Length Path prefixes before conversion: - `\\?\C:\path` -> `C:\path` -> `file:///C:/path` - `\\?\UNC\server\share` -> `\\server\share` -> `file://server/share` Previously, `toUri(r'\\?\UNC\wsl.localhost\Ubuntu-24.04\tmp\foo')` threw `FormatException` because the `\\?\UNC` prefix was misinterpreted as a UNC root with `?` as the host. This made `path.toUri()` inconsistent with `Uri.file()`, which already handles these prefixes correctly. Fixes dart-lang/core#980 --- pkgs/path/CHANGELOG.md | 4 ++++ pkgs/path/lib/src/style/windows.dart | 29 ++++++++++++++++++++-------- pkgs/path/test/windows_test.dart | 25 ++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/pkgs/path/CHANGELOG.md b/pkgs/path/CHANGELOG.md index b309d17a..38006305 100644 --- a/pkgs/path/CHANGELOG.md +++ b/pkgs/path/CHANGELOG.md @@ -1,5 +1,9 @@ ## 1.9.2-wip +- `WindowsStyle.absolutePathToUri` (and therefore `toUri()`) now strips Windows + Extended-Length Path prefixes (`\\?\` and `\\?\UNC\`) instead of + throwing a `FormatException`. This aligns `path.toUri()` with + `Uri.file()` / `Uri.directory()` from `dart:io`. - Make `Url` style better at recognizing schemes and authorities. Only consider a path as being a schemed URL if it starts with a valid scheme. diff --git a/pkgs/path/lib/src/style/windows.dart b/pkgs/path/lib/src/style/windows.dart index 14c2d8bd..911045eb 100644 --- a/pkgs/path/lib/src/style/windows.dart +++ b/pkgs/path/lib/src/style/windows.dart @@ -103,6 +103,24 @@ class WindowsStyle extends InternalStyle { @override Uri absolutePathToUri(String path) { + // Strip Windows Extended-Length Path prefixes so that `toUri()` aligns + // with `Uri.file()` / `Uri.directory()` from `dart:io`. + // + // `\\?\C:\path` -> `C:\path` + // `\\?\UNC\server\share` -> `\\server\share` + // + // The `\\?\` prefix is a Win32 API artifact (enables paths > 260 chars + // and disables DOS device-name interpretation). It is not user intent and + // should not leak into the resulting file URI. See: + // https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#win32-file-namespaces + if (path.startsWith(r'\\?\')) { + if (path.startsWith(r'\\?\UNC\')) { + path = r'\\' + path.substring(8); + } else { + path = path.substring(4); + } + } + final parsed = ParsedPath.parse(path, this); if (parsed.root!.startsWith(r'\\')) { // Network paths become "file://server/share/path/to/file". @@ -119,10 +137,7 @@ class WindowsStyle extends InternalStyle { } return Uri( - scheme: 'file', - host: rootParts.first, - pathSegments: parsed.parts, - ); + scheme: 'file', host: rootParts.first, pathSegments: parsed.parts); } else { // Drive-letter paths become "file:///C:/path/to/file". @@ -136,10 +151,8 @@ class WindowsStyle extends InternalStyle { // Get rid of the trailing "\" in "C:\" because the URI constructor will // add a separator on its own. - parsed.parts.insert( - 0, - parsed.root!.replaceAll('/', '').replaceAll('\\', ''), - ); + parsed.parts + .insert(0, parsed.root!.replaceAll('/', '').replaceAll('\\', '')); return Uri(scheme: 'file', pathSegments: parsed.parts); } diff --git a/pkgs/path/test/windows_test.dart b/pkgs/path/test/windows_test.dart index 88ceb3a0..c73098d2 100644 --- a/pkgs/path/test/windows_test.dart +++ b/pkgs/path/test/windows_test.dart @@ -1058,6 +1058,31 @@ void main() { Uri.parse('_%7B_%7D_%60_%5E_%20_%22_%25_'), ); expect(context.toUri(''), Uri.parse('')); + + // Windows Extended-Length Path prefixes are stripped to align with + // `Uri.file()` from `dart:io`. See: + // https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#win32-file-namespaces + expect( + context.toUri(r'\\?\UNC\server\share\path\to\foo'), + Uri.parse('file://server/share/path/to/foo'), + ); + expect( + context.toUri(r'\\?\UNC\wsl.localhost\Ubuntu-24.04\tmp\foo'), + Uri.parse('file://wsl.localhost/Ubuntu-24.04/tmp/foo'), + ); + expect( + context.toUri(r'\\?\UNC\server\share'), + Uri.parse('file://server/share'), + ); + expect( + context.toUri(r'\\?\UNC\server\share\'), + Uri.parse('file://server/share/'), + ); + expect( + context.toUri(r'\\?\C:\path\to\foo'), + Uri.parse('file:///C:/path/to/foo'), + ); + expect(context.toUri(r'\\?\C:\'), Uri.parse('file:///C:/')); }); group('prettyUri', () { From 214c34b9305b2d603f4fc211c35b27d506477818 Mon Sep 17 00:00:00 2001 From: Berial <715434384@qq.com> Date: Thu, 23 Jul 2026 14:31:37 +0800 Subject: [PATCH 2/3] Trigger CLA recheck after signing From 5424d7b53ac5ca66316ccf10251646218364ec1d Mon Sep 17 00:00:00 2001 From: Berial <715434384@qq.com> Date: Mon, 27 Jul 2026 13:58:06 +0800 Subject: [PATCH 3/3] [path] Apply review feedback: use length-based indexing in absolutePathToUri --- pkgs/path/lib/src/style/windows.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/path/lib/src/style/windows.dart b/pkgs/path/lib/src/style/windows.dart index 911045eb..246809ec 100644 --- a/pkgs/path/lib/src/style/windows.dart +++ b/pkgs/path/lib/src/style/windows.dart @@ -114,10 +114,10 @@ class WindowsStyle extends InternalStyle { // should not leak into the resulting file URI. See: // https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#win32-file-namespaces if (path.startsWith(r'\\?\')) { - if (path.startsWith(r'\\?\UNC\')) { - path = r'\\' + path.substring(8); + if (path.startsWith(r'UNC\', r'\\?\'.length)) { + path = path.replaceRange(0, r'\\?\UNC'.length, r'\'); } else { - path = path.substring(4); + path = path.substring(r'\\?\'.length); } }