mirror of
https://github.com/openai/codex.git
synced 2026-11-14 06:48:26 +00:00
Normalize Windows namespace paths in path URIs (#35851)
## What changed - Convert Windows device-namespace drive paths such as `\\?\D:\reports` and `\\.\D:\reports` to canonical drive-based `file:` URIs. - Convert device-namespace UNC paths to canonical hosted `file:` URIs. - Preserve unsupported or ambiguous namespace paths as opaque path URIs instead of interpreting them as filesystem paths. ## Testing Add coverage for namespace path conversion and joining, including malformed UNC paths, reserved device names, and volume identifiers. GitOrigin-RevId: 234f69bc2787e5d318bc0320b94e7392abfb5602
This commit is contained in:
1 parent
101d6b8cb2
commit
8707a35113
3 files changed
+88
-1
No files matched your search
@@ -150,7 +150,8 @@ fn normalize_path_for_platform(path: &Path) -> Cow<'_, Path> {
|
|||||||
Cow::Borrowed(path)
|
Cow::Borrowed(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn normalize_windows_device_path(path: &str) -> Option<String> {
|
/// Normalizes Windows drive and UNC namespace aliases on any host.
|
||||||
|
pub fn normalize_windows_device_path(path: &str) -> Option<String> {
|
||||||
if let Some(unc) = path.strip_prefix(r"\\?\UNC\") {
|
if let Some(unc) = path.strip_prefix(r"\\?\UNC\") {
|
||||||
return Some(format!(r"\\{unc}"));
|
return Some(format!(r"\\{unc}"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||||
|
use codex_utils_absolute_path::normalize_windows_device_path;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Deserializer;
|
use serde::Deserializer;
|
||||||
@@ -661,6 +662,30 @@ fn parse_posix_path(path: &str) -> Option<PathUri> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse_windows_path(path: &str) -> Option<PathUri> {
|
fn parse_windows_path(path: &str) -> Option<PathUri> {
|
||||||
|
if let Some(normalized_path) = normalize_windows_device_path(path) {
|
||||||
|
if let Some(unc_path) = normalized_path.strip_prefix(r"\\") {
|
||||||
|
let mut components = unc_path.split(is_windows_separator_char);
|
||||||
|
if matches!(components.next(), None | Some("" | "." | ".."))
|
||||||
|
|| matches!(components.next(), None | Some("" | "." | ".."))
|
||||||
|
{
|
||||||
|
return Some(windows_opaque_path_uri(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Some(
|
||||||
|
parse_unnormalized_windows_path(&normalized_path)
|
||||||
|
.filter(|uri| {
|
||||||
|
uri.infer_path_convention() == Some(PathConvention::Windows)
|
||||||
|
&& uri.opaque_fallback_bytes().is_none()
|
||||||
|
&& (!normalized_path.starts_with(r"\\")
|
||||||
|
|| uri.0.host_str().is_some_and(|host| !host.is_empty()))
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| windows_opaque_path_uri(path)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
parse_unnormalized_windows_path(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_unnormalized_windows_path(path: &str) -> Option<PathUri> {
|
||||||
let bytes = path.as_bytes();
|
let bytes = path.as_bytes();
|
||||||
let uses_namespace = matches!(
|
let uses_namespace = matches!(
|
||||||
bytes,
|
bytes,
|
||||||
|
|||||||
@@ -738,6 +738,26 @@ fn join_normalizes_absolute_parent_segments() {
|
|||||||
r"\\server\share\a\..\b",
|
r"\\server\share\a\..\b",
|
||||||
"file://server/share/b",
|
"file://server/share/b",
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"file:///C:/workspace",
|
||||||
|
r"\\?\D:\reports\report.pdf",
|
||||||
|
"file:///D:/reports/report.pdf",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"file:///C:/workspace",
|
||||||
|
r"\\.\D:\reports\report.pdf",
|
||||||
|
"file:///D:/reports/report.pdf",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"file:///C:/workspace",
|
||||||
|
r"\\?\UNC\server\share\reports\report.pdf",
|
||||||
|
"file://server/share/reports/report.pdf",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"file:///C:/workspace",
|
||||||
|
r"\\.\UNC\server\share\reports\report.pdf",
|
||||||
|
"file://server/share/reports/report.pdf",
|
||||||
|
),
|
||||||
("file:///workspace", "/tmp//a/../b", "file:///tmp/b"),
|
("file:///workspace", "/tmp//a/../b", "file:///tmp/b"),
|
||||||
("file:///workspace", "/tmp/a/..//b", "file:///tmp/b"),
|
("file:///workspace", "/tmp/a/..//b", "file:///tmp/b"),
|
||||||
("file:///workspace", "/tmp/a///../b", "file:///tmp/b"),
|
("file:///workspace", "/tmp/a///../b", "file:///tmp/b"),
|
||||||
@@ -765,6 +785,47 @@ fn join_normalizes_absolute_parent_segments() {
|
|||||||
] {
|
] {
|
||||||
let base = PathUri::parse(base).expect("valid base URI");
|
let base = PathUri::parse(base).expect("valid base URI");
|
||||||
let expected = PathUri::parse(expected).expect("valid expected URI");
|
let expected = PathUri::parse(expected).expect("valid expected URI");
|
||||||
|
if normalize_windows_device_path(path).is_some() {
|
||||||
|
assert_eq!(
|
||||||
|
LegacyAppPathString::from_string(path).to_path_uri(PathConvention::Windows),
|
||||||
|
Ok(expected.clone()),
|
||||||
|
"converting {path}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert_eq!(base.join(path), Ok(expected), "joining {path}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn windows_namespace_normalization_preserves_opaque_paths() {
|
||||||
|
let base = PathUri::parse("file:///C:/workspace").expect("valid Windows base URI");
|
||||||
|
|
||||||
|
for path in [
|
||||||
|
r"\\?\UNC\server",
|
||||||
|
r"\\.\UNC\server",
|
||||||
|
r"\\?\UNC\localhost\share\report.pdf",
|
||||||
|
r"\\.\UNC\LOCALHOST\share\report.pdf",
|
||||||
|
r"\\?\UNC\.\share\report.pdf",
|
||||||
|
r"\\.\UNC\..\share\report.pdf",
|
||||||
|
r"\\?\UNC\server\.\report.pdf",
|
||||||
|
r"\\.\UNC\server\..\report.pdf",
|
||||||
|
r"\\?\UNC\?\UNC\?\C:\report.pdf",
|
||||||
|
r"\\.\UNC\?\UNC\?\C:\report.pdf",
|
||||||
|
r"\\.\COM1",
|
||||||
|
r"\\?\Volume{00000000-0000-0000-0000-000000000000}\report.pdf",
|
||||||
|
] {
|
||||||
|
let expected = windows_opaque_path_uri(path);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
PathUri::from_absolute_native_path(path, PathConvention::Windows),
|
||||||
|
Some(expected.clone()),
|
||||||
|
"parsing {path}"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
LegacyAppPathString::from_string(path).to_path_uri(PathConvention::Windows),
|
||||||
|
Ok(expected.clone()),
|
||||||
|
"converting {path}"
|
||||||
|
);
|
||||||
assert_eq!(base.join(path), Ok(expected), "joining {path}");
|
assert_eq!(base.join(path), Ok(expected), "joining {path}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user