path-uri: render native paths across platforms (#27819)

## Why

We're moving to `PathUri` in more places to support cross-OS
app-server/exec-server, but we don't want to expose the URI encoding to
users of app-server's public APIs yet.

We'll need to translate at the app-server API boundary between
client-visible "regular" paths that are appropriate for the OS of the
environment for which the paths make sense, which means using the
environment's path personality to do the conversion.

`PathUri` doesn't yet attempt to encode environment ID, so for now we'll
sniff the most likely path convention for a given path.

## What

- Add `PathConvention` and `NativePathString` with host-independent
POSIX, Windows drive, and UNC rendering.
- Cover cross-host rendering, encoding, Unicode, invalid components.
This commit is contained in:
Adam Perry @ OpenAI authored and GitHub committed 2026-06-14 05:26:49 +00:00
1 parent 42dec90bc4
commit 5e9249ec02
4 files changed
+1017 -14

No files matched your search

+84 -14
View File
@@ -17,6 +17,12 @@ use thiserror::Error;
use ts_rs::TS;
use url::Url;
mod api_path_string;
pub use api_path_string::ApiPathString;
pub use api_path_string::ApiPathStringError;
pub use api_path_string::PathConvention;
pub const FILE_SCHEME: &str = "file";
const BAD_PATH_URI_PREFIX: &str = "file:///%00/bad/path/";
@@ -29,15 +35,14 @@ const BAD_PATH_URI_PREFIX: &str = "file:///%00/bad/path/";
/// created by [`Self::from_abs_path`] are opaque to these lexical operations.
///
/// `file:` paths retain their URI spelling so they can be parsed independently
/// of the current host. In particular, `/C:/src` remains ambiguous between a
/// Windows drive path and a valid POSIX path until [`Self::to_abs_path`]
/// applies the current host's rules. A local POSIX `file:` URI can also retain
/// of the current host. A local POSIX `file:` URI can also retain
/// percent-encoded non-UTF-8 bytes for lossless native round trips.
///
/// Like [VS Code resources], path operations use `/` URI separators on every
/// host. They preserve a URL authority but do not infer Windows drive or UNC
/// roots from path text. Native path normalization, filesystem aliases,
/// symlinks, case sensitivity, and Unicode normalization are not resolved.
/// host. Lexical path operations preserve a URL authority without interpreting
/// Windows drive or UNC roots from path text. Native path normalization,
/// filesystem aliases, symlinks, case sensitivity, and Unicode normalization
/// are not resolved.
///
/// Serde represents a `PathUri` as its canonical URI string. Deserialization
/// also accepts an absolute native path for compatibility with fields that
@@ -76,22 +81,24 @@ impl PathUri {
}
#[cfg(unix)]
let encoded_path = {
let path_bytes = {
use std::os::unix::ffi::OsStrExt;
base64::engine::general_purpose::URL_SAFE_NO_PAD
.encode(path.as_path().as_os_str().as_bytes())
path.as_path().as_os_str().as_bytes().to_vec()
};
#[cfg(windows)]
let encoded_path = {
let path_bytes = {
use std::os::windows::ffi::OsStrExt;
let path_bytes = path
.as_path()
path.as_path()
.as_os_str()
.encode_wide()
.flat_map(u16::to_le_bytes)
.collect::<Vec<_>>();
base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(path_bytes)
.collect::<Vec<_>>()
};
Self::from_opaque_path_bytes(&path_bytes)
}
fn from_opaque_path_bytes(path_bytes: &[u8]) -> Self {
let encoded_path = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(path_bytes);
let Ok(uri) = Self::parse(&format!("{BAD_PATH_URI_PREFIX}{encoded_path}")) else {
unreachable!("URL-safe base64 always produces a valid fallback path URI");
};
@@ -117,6 +124,46 @@ impl PathUri {
self.0.path()
}
fn opaque_fallback_bytes(&self) -> Option<Vec<u8>> {
decode_bad_path_uri(&self.0)
}
/// Infers the native path convention represented by this URI.
///
/// A URI authority is treated as a Windows UNC host, and a leading
/// drive-letter segment such as `C:` is treated as a Windows drive. All
/// other ordinary file URIs are treated as POSIX paths. This deliberately
/// classifies `file:///C:/src` as Windows even though `/C:/src` is also a
/// valid POSIX path. In practice, POSIX paths with a drive-shaped first
/// component are rare enough that recognizing foreign Windows paths is the
/// more useful default.
///
/// Opaque fallback URIs are inspected for an absolute POSIX byte prefix or
/// an absolute Windows UTF-16LE prefix. `None` is returned when their
/// payload does not identify either convention.
///
/// TODO(anp): Once `PathUri` carries an environment identifier, prefer the
/// environment's declared convention over this spelling-based heuristic.
pub fn infer_path_convention(&self) -> Option<PathConvention> {
if let Some(path_bytes) = self.opaque_fallback_bytes() {
return infer_opaque_path_convention(&path_bytes);
}
if self.0.host_str().is_some() {
return Some(PathConvention::Windows);
}
let has_windows_drive = self
.0
.path_segments()
.and_then(|mut segments| segments.find(|segment| !segment.is_empty()))
.is_some_and(is_windows_drive_uri_segment);
if has_windows_drive {
Some(PathConvention::Windows)
} else {
Some(PathConvention::Posix)
}
}
/// Returns the decoded final URI path segment, or `None` for the URI root
/// or an opaque fallback URI created by [`Self::from_abs_path`].
///
@@ -377,6 +424,29 @@ fn decode_bad_path_uri(url: &Url) -> Option<Vec<u8>> {
.then_some(path_bytes)
}
fn is_windows_drive_uri_segment(segment: &str) -> bool {
matches!(segment.as_bytes(), [drive, b':'] if drive.is_ascii_alphabetic())
}
fn infer_opaque_path_convention(path_bytes: &[u8]) -> Option<PathConvention> {
if path_bytes.starts_with(b"/") {
return Some(PathConvention::Posix);
}
if !path_bytes.len().is_multiple_of(2) {
return None;
}
let mut path_wide = path_bytes
.chunks_exact(2)
.map(|bytes| u16::from_le_bytes([bytes[0], bytes[1]]));
let first = path_wide.next()?;
let second = path_wide.next()?;
let has_drive = u8::try_from(first).is_ok_and(|drive| drive.is_ascii_alphabetic())
&& second == u16::from(b':');
let has_unc_prefix = first == u16::from(b'\\') && second == u16::from(b'\\');
(has_drive || has_unc_prefix).then_some(PathConvention::Windows)
}
/// Rejects URI metadata that has no defined meaning for `file:` URIs.
fn validate_common_known_uri(url: &Url) -> Result<(), PathUriParseError> {
if !url.username().is_empty() || url.password().is_some() {