Repository navigation
[path] \WindowsStyle.absolutePathToUri\ throws FormatException on Windows Extended-Length UNC paths #980
Description
Summary
package:path's WindowsStyle.absolutePathToUri throws FormatException when given a Windows Extended-Length UNC path of the form \\?\UNC\server\share\.... These paths are returned by Directory.resolveSymbolicLinksSync() (which calls the Windows API GetFinalPathNameByHandle) for any UNC path, and are also the documented way to bypass MAX_PATH limits on Windows.
Note: this is not the root cause of Analysis Server failing on WSL projects (that's analyzer's _PhysicalResource.exists missing try-catch, filed separately). But it is a real bug that affects any code calling path.toUri() on a resolveSymbolicLinksSync() result.
Reproduction
pubspec.yaml:
name: test_path_unc
environment:
sdk: ^3.0.0
dependencies:
path: ^1.9.0test.dart:
import 'dart:io';
import 'package:path/path.dart' as p;
void main() {
final resolved = Directory.current.resolveSymbolicLinksSync();
print('resolved: $resolved');
final uri = p.toUri(resolved); // <- throws
print('uri: $uri');
}Run from a UNC/mapped-drive directory:
cd W:\tmp\test_path_unc (W: -> \\wsl.localhost\Ubuntu-24.04)
dart run test.dart
Actual output
resolved: \\?\UNC\wsl.localhost\Ubuntu-24.04\tmp\test_path_unc
FormatException: Invalid character (at character 1)
?
^
#0 _Uri._fail (dart:core/uri.dart:2050:5)
#1 _Uri._normalizeRegName (dart:core/uri.dart:2598:9)
#2 _Uri._makeHost (dart:core/uri.dart:2453:12)
#3 new _Uri (dart:core/uri.dart:1955:12)
#4 WindowsStyle.absolutePathToUri (package:path/src/style/windows.dart:121:14)
#5 Context.toUri (package:path/src/context.dart:1062:20)
#6 toUri (package:path/path.dart:459:35)
Boundary test
| Input | Result |
|---|---|
\\?\UNC\wsl.localhost\Ubuntu-24.04\home\berial |
FAIL |
\\?\UNC\localhost\c$\Users |
FAIL |
\\?\UNC\127.0.0.1\c$\Users |
FAIL |
\\?\UNC\someserver\share\path |
FAIL (even non-existent server) |
\\wsl.localhost\Ubuntu-24.04\home\berial |
OK |
\\localhost\c$\Users |
OK |
\\someserver\share\path |
OK |
W:\home\berial |
OK |
So the bug is not WSL-specific — it affects all Windows Extended UNC paths (\\?\UNC\*).
Note: Uri.file() (from dart:core) handles \\?\UNC\... correctly (strips the prefix), so this is specifically a package:path issue, not a dart:core issue.
Root cause
pkgs/path/lib/src/style/windows.dart:29:
final rootPattern = RegExp(r'^(\\\\[^\\]+\\[^\\/]+|[a-zA-Z]:[/\\])');The first alternative \\\\[^\\]+\\[^\\/]+ is meant to match \\server\share, but it also matches \\?\UNC because [^\\]+ happily matches ?.
Then in absolutePathToUri (line 121):
final rootParts = parsed.root!.split('\\').where((part) => part != '');
parsed.parts.insert(0, rootParts.last);
// ...
return Uri(scheme: 'file', host: rootParts.first, pathSegments: parsed.parts);parsed.root=\\?\UNCsplit('\\')→['', '', '?', 'UNC']where((part) => part != '')→['?', 'UNC']rootParts.first='?'Uri(host: '?')→ FormatException (?is not a valid URI host)
Suggested fix
Either strip the \\?\ / \\?\UNC\ prefix before parsing, or refine the rootPattern to not match \\?\:
// Option A: strip extended-length prefix in absolutePathToUri
Uri absolutePathToUri(String path) {
if (path.startsWith(r'\\?\UNC\\')) {
path = r'\\' + path.substring(8);
} else if (path.startsWith(r'\\?\')) {
path = path.substring(4);
}
// ... existing logic
}// Option B: refine rootPattern
final rootPattern = RegExp(r'^(\\\\[^\\?][^\\]*\\[^\\/]+|[a-zA-Z]:[/\\])');Environment
- Dart SDK: 3.11.5 (stable) on
windows_x64 - package:path: 1.9.1 (also confirmed
mainbranch is affected) - OS: Windows 11 + WSL2 (Ubuntu-24.04)
Activity
lrhn commentedon Jul 20, 2026
Windows paths are ... complicated.
First thing to decide is always what the intended behavior should be. When there are multiple paths that mean the same thing, the code recognize that they mean the same thing and give the same answer to all queries (that don't actually look for the syntactic difference).
For a Windows path of
\\?\UNC\wsl.localhost\Ubuntu-24.04\tmp\test_path_unc, what should the corresponding URI be.One option is to preserve the entire path:
file:////?/UNC/wsl.localhost/Ubuntu-24.04/tmp/test_path_unc.Another is to recognize
wsl.localhostas a host name, and?\UNC\as an ignorable prefix:file://wsl.localhost/Ubutut-24.04/tmp/test_path_unc.Either can work.
I agree that it shouldn't throw a spurious error. It it's an error, it should be a deliberate one.
(Usual reference for Windows paths: https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats)
berial commentedon Jul 21, 2026
Hi @lrhn, thanks for the response.
I'd recommend option B (recognize
wsl.localhostas host, strip?\UNC\prefix):Rationale:
Consistency with
Uri.file()— Dart SDK'sUri.file()already does this.Uri.file(r'\\?\UNC\wsl.localhost\Ubuntu-24.04\tmp\foo')correctly returnsfile://wsl.localhost/Ubuntu-24.04/tmp/foowithout throwing. Havingpath.toUri()throw on the same input creates an inconsistency that bites real-world tools (e.g., analyzer internals).WSL paths are a real Windows use case —
\\wsl.localhost\<distro>\...is how Windows natively exposes WSL filesystems. Users hit this path format whenever they cross the Windows/WSL boundary, which is increasingly common.Option A (
file:////?/UNC/...) leaks implementation detail — the\\?\prefix is a Windows API artifact (GetFinalPathNameByHandle always returns it for UNC paths), not user intent. Exposing it in URIs just pushes the problem downstream.Repro:
Happy to submit a PR if you confirm the direction.
lrhn commentedon Jul 22, 2026
Makes sense. Windows is complicated, but that doesn't need to leak to the user.
There are the path length limits, where
\\?\UNC\C:\longpathdoesn't have the same limit on length asC:\longpath, but that should be handled in thedart:iooperations, not by the user. (If we pass in a potentially-too-long path on Windows, which isn't already UNC, prefix it with\\?\UNCin the call toCreateFile, not before.)Same if the Dart string path contains a reserved device name, then prefixing
\\?\UNCshould turn off that. Again something that can be done just before passing the path to Windows, the Dart path doesn't need to know that. (Or probably, always prefix with\\?\UNCunless the path already starts with\\?\.That would mean that a Dart
File.pathon Windows is an abstraction over the Windows file path system.It removes information, which may not always be what a user wants.
We should be very clear, up-front, about which parts are removed, which are retained, and when that happens.
Maybe we just need to see what
Uri.fromFiledoes, and copy that.And then document it, because if we don't have a written specification of what's going on, we'll certainly be inconsistent.
berial commentedon Jul 23, 2026
I've opened #981 with a fix. All tests pass, and the CLA has been signed.
lrhn commentedon Jul 24, 2026
Change seems reasonable to me. (Not an expert, all I know I read on the interwebs.)