Infer Windows UNC paths with forward and mixed slashes (#49424)

## Why

`LegacyAppPathString` treated `//server/share/project` as POSIX, losing its UNC interpretation when inferring a `PathUri`.

## What changed

Infer Windows syntax whenever an API path starts with two separators, including forward and mixed slashes, independently of the host. For example, `//server/share/project` now converts to `file://server/share/project`. Double-slash POSIX paths require an explicit `PathConvention::Posix`; inferred paths still undergo validation.

## Testing

Add regression tests for UNC and namespace paths, mixed separators, URI serialization round trips, explicit POSIX conversion, convention mismatches, and malformed UNC paths.

GitOrigin-RevId: 720893de517addb7a938e6784d1ed0f05df3ec0d
This commit is contained in:
Dmitriy Kovalenko
2026-09-30 01:28:02 +00:00
committed by copyberry
parent ab84d71f57
commit d1d0e89558
2 changed files with 71 additions and 5 deletions
+11 -4
View File
@@ -162,9 +162,11 @@ impl LegacyAppPathString {
/// Infers the path convention of an absolute API path from its spelling.
///
/// Relative paths and ambiguous spellings return `None`. In particular,
/// slash-prefixed paths are treated as POSIX even when they could also be
/// interpreted as slash-delimited Windows UNC paths.
/// Two leading separators select Windows UNC or namespace syntax, including
/// forward and mixed slashes, independently of the current host. This favors
/// UNC paths over ambiguous double-slash POSIX paths. Call [`Self::to_path_uri`]
/// with an explicit POSIX convention to preserve that interpretation.
/// Relative paths return `None`; inferred prefixes still require validation.
pub fn infer_absolute_path_convention(&self) -> Option<PathConvention> {
let bytes = self.0.as_bytes();
let has_windows_drive_root = matches!(
@@ -172,7 +174,12 @@ impl LegacyAppPathString {
[drive, b':', separator, ..]
if drive.is_ascii_alphabetic() && is_windows_separator_byte(*separator)
);
if has_windows_drive_root || self.0.starts_with(r"\\") {
let has_windows_unc_root = matches!(
bytes,
[first, second, ..]
if is_windows_separator_byte(*first) && is_windows_separator_byte(*second)
);
if has_windows_drive_root || has_windows_unc_root {
Some(PathConvention::Windows)
} else if self.0.starts_with('/') {
Some(PathConvention::Posix)