mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
[codex] make PathUri::from_abs_path infallible (#27976)
## Why `PathUri::from_abs_path` can fail for absolute paths that do not have a normal `file:` URI representation, forcing filesystem call sites to handle a conversion error even though the original path can be preserved losslessly. ## What Make `from_abs_path` infallible and migrate its callers. Unrepresentable paths use `file:///%00/bad/path/<base64>`, encoding Unix bytes or Windows UTF-16LE; `to_abs_path` validates and decodes that fallback. The leading encoded null reserves a namespace that cannot collide with a real Unix or Windows path, and fallback URIs remain opaque to lexical path operations. ## Validation Added path-URI coverage for Unix null and non-UTF-8 paths, Windows device/verbatim and non-Unicode paths, serialization, malformed fallbacks, opaque lexical operations, invalid native payloads, and literal `/bad/path` collision resistance.
This commit is contained in:
1 parent
eb46984aaa
commit
968a3ac9c1
42 files changed
+356
-323
No files matched your search
@@ -1,9 +1,12 @@
|
||||
use super::*;
|
||||
use codex_utils_absolute_path::AbsolutePathBufGuard;
|
||||
use pretty_assertions::assert_eq;
|
||||
#[cfg(windows)]
|
||||
use std::ffi::OsString;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
#[cfg(unix)]
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::ffi::OsStringExt;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
@@ -12,7 +15,7 @@ fn file_uri_round_trips_an_absolute_path() {
|
||||
.expect("current directory")
|
||||
.join("a path/file.rs");
|
||||
|
||||
let uri = PathUri::from_abs_path(&path).expect("path should convert to a file URI");
|
||||
let uri = PathUri::from_abs_path(&path);
|
||||
|
||||
let uri_string = uri.to_string();
|
||||
assert!(uri_string.starts_with("file:"));
|
||||
@@ -57,24 +60,136 @@ fn file_uri_parses_a_windows_path_on_any_host() {
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn file_uri_rejects_windows_prefixes_without_a_uri_representation() {
|
||||
for native_path in [
|
||||
r"\\.\COM1",
|
||||
r"\\?\Volume{00000000-0000-0000-0000-000000000000}\file.rs",
|
||||
fn file_uri_falls_back_for_windows_prefixes_without_a_uri_representation() {
|
||||
for (native_path, expected_uri) in [
|
||||
(r"\\.\COM1", "file:///%00/bad/path/XABcAC4AXABDAE8ATQAxAFwA"),
|
||||
(
|
||||
r"\\?\Volume{00000000-0000-0000-0000-000000000000}\file.rs",
|
||||
"file:///%00/bad/path/XABcAD8AXABWAG8AbAB1AG0AZQB7ADAAMAAwADAAMAAwADAAMAAtADAAMAAwADAALQAwADAAMAAwAC0AMAAwADAAMAAtADAAMAAwADAAMAAwADAAMAAwADAAMAAwAH0AXABmAGkAbABlAC4AcgBzAA",
|
||||
),
|
||||
] {
|
||||
let path = AbsolutePathBuf::from_absolute_path_checked(native_path)
|
||||
.expect("Windows namespace path should be absolute");
|
||||
|
||||
let uri = PathUri::from_abs_path(&path);
|
||||
|
||||
assert_eq!(uri.to_string(), expected_uri, "converting {native_path}");
|
||||
assert_eq!(
|
||||
PathUri::from_abs_path(&path)
|
||||
.expect_err("Windows namespace path should not convert")
|
||||
.kind(),
|
||||
io::ErrorKind::InvalidInput,
|
||||
"converting {native_path}"
|
||||
PathUri::parse(&uri.to_string())
|
||||
.expect("fallback URI should parse")
|
||||
.to_abs_path()
|
||||
.expect("fallback URI should decode"),
|
||||
path,
|
||||
"round-tripping {native_path}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn file_uri_fallback_round_trips_non_unicode_windows_paths() {
|
||||
let path_wide = r"C:\bad\"
|
||||
.encode_utf16()
|
||||
.chain([0xd800])
|
||||
.collect::<Vec<_>>();
|
||||
let path = PathBuf::from(OsString::from_wide(&path_wide));
|
||||
let path = AbsolutePathBuf::from_absolute_path_checked(path).expect("absolute Windows path");
|
||||
|
||||
let uri = PathUri::from_abs_path(&path);
|
||||
let reparsed = PathUri::parse(&uri.to_string()).expect("fallback URI should parse");
|
||||
|
||||
assert!(uri.to_string().starts_with(BAD_PATH_URI_PREFIX));
|
||||
assert_eq!(
|
||||
reparsed.to_abs_path().expect("fallback URI should decode"),
|
||||
path
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn file_uri_falls_back_for_posix_paths_with_null_bytes() {
|
||||
let path = PathBuf::from(std::ffi::OsString::from_vec(
|
||||
b"/tmp/null-\0-\xff-byte".to_vec(),
|
||||
));
|
||||
let path = AbsolutePathBuf::from_absolute_path_checked(path).expect("absolute POSIX path");
|
||||
|
||||
let uri = PathUri::from_abs_path(&path);
|
||||
|
||||
assert_eq!(
|
||||
uri,
|
||||
PathUri::parse("file:///%00/bad/path/L3RtcC9udWxsLQAt_y1ieXRl")
|
||||
.expect("valid fallback URI")
|
||||
);
|
||||
let json = serde_json::to_string(&uri).expect("fallback URI should serialize");
|
||||
let reparsed: PathUri =
|
||||
serde_json::from_str(&json).expect("serialized fallback URI should parse");
|
||||
assert_eq!(json, r#""file:///%00/bad/path/L3RtcC9udWxsLQAt_y1ieXRl""#);
|
||||
assert_eq!(reparsed, uri);
|
||||
assert_eq!(
|
||||
reparsed.to_abs_path().expect("fallback URI should decode"),
|
||||
path
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn ordinary_bad_path_uri_is_not_decoded_as_a_fallback() {
|
||||
let path = AbsolutePathBuf::from_absolute_path_checked("/bad/path/L3RtcC9udWxsLQAt_y1ieXRl")
|
||||
.expect("absolute POSIX path");
|
||||
let uri = PathUri::from_abs_path(&path);
|
||||
|
||||
assert_eq!(uri.to_string(), "file:///bad/path/L3RtcC9udWxsLQAt_y1ieXRl");
|
||||
assert_eq!(
|
||||
uri.to_abs_path().expect("URI should convert literally"),
|
||||
path
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_bad_path_uris_are_rejected() {
|
||||
for uri in [
|
||||
"file:///%00/bad/path/",
|
||||
"file:///%00/bad/path/not*base64",
|
||||
"file:///%00/bad/path/YQ==",
|
||||
"file:///%00/bad/path/YR",
|
||||
"file:///%00/bad/path/YQ/extra",
|
||||
"file:///%00/other/YQ",
|
||||
] {
|
||||
assert_eq!(
|
||||
PathUri::parse(uri),
|
||||
Err(PathUriParseError::InvalidFileUriPath),
|
||||
"parsing {uri}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn structurally_valid_bad_path_uri_with_invalid_native_payload_fails_conversion() {
|
||||
let uri = PathUri::parse("file:///%00/bad/path/YQ")
|
||||
.expect("canonical base64 fallback URI should parse");
|
||||
|
||||
assert_eq!(
|
||||
uri.to_abs_path()
|
||||
.expect_err("relative fallback payload should not convert")
|
||||
.kind(),
|
||||
io::ErrorKind::InvalidInput
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_path_uris_are_opaque_to_lexical_operations() {
|
||||
let uri = PathUri::parse("file:///%00/bad/path/YQ")
|
||||
.expect("canonical base64 fallback URI should parse");
|
||||
|
||||
assert_eq!(uri.basename(), None);
|
||||
assert_eq!(uri.parent(), None);
|
||||
assert_eq!(uri.join(""), Ok(uri.clone()));
|
||||
assert_eq!(
|
||||
uri.join("child"),
|
||||
Err(PathUriParseError::InvalidFileUriPath)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn file_uri_parses_a_posix_path_on_any_host() {
|
||||
let uri = PathUri::parse("file:///home/alice/src/main.rs")
|
||||
@@ -101,7 +216,7 @@ fn file_uri_accepts_non_utf8_posix_paths() {
|
||||
let path = PathBuf::from(std::ffi::OsString::from_vec(b"/tmp/non-utf8-\xff".to_vec()));
|
||||
let path = AbsolutePathBuf::from_absolute_path_checked(path).expect("absolute POSIX path");
|
||||
|
||||
let uri = PathUri::from_abs_path(&path).expect("non-UTF-8 path should convert to a file URI");
|
||||
let uri = PathUri::from_abs_path(&path);
|
||||
assert_eq!(
|
||||
uri.to_abs_path()
|
||||
.expect("URI should convert to native path"),
|
||||
@@ -127,7 +242,7 @@ fn file_uri_round_trips_literal_percent_characters() {
|
||||
fn file_uri_round_trips_windows_unc_paths() {
|
||||
let path = AbsolutePathBuf::from_absolute_path_checked(r"\\server\share\src\main.rs")
|
||||
.expect("absolute UNC path");
|
||||
let uri = PathUri::from_abs_path(&path).expect("UNC path should convert to a file URI");
|
||||
let uri = PathUri::from_abs_path(&path);
|
||||
|
||||
assert_eq!(uri.encoded_path(), "/share/src/main.rs");
|
||||
assert_eq!(uri.to_abs_path().expect("UNC URI should convert"), path);
|
||||
@@ -198,10 +313,7 @@ fn path_uri_deserializes_legacy_absolute_paths() {
|
||||
let json = serde_json::to_string(&path).expect("absolute path should serialize");
|
||||
let uri: PathUri = serde_json::from_str(&json).expect("legacy absolute path should parse");
|
||||
|
||||
assert_eq!(
|
||||
uri,
|
||||
PathUri::from_abs_path(&path).expect("expected file URI")
|
||||
);
|
||||
assert_eq!(uri, PathUri::from_abs_path(&path));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user