mirror of
https://github.com/openai/codex.git
synced 2026-11-13 22:47:20 +00:00
Harden Windows file URI conversion (#40423)
## What changed - Recognize percent-encoded drive colons when inferring Windows paths, rendering native path strings, and resolving same-drive relative joins. - Reject Windows file URIs with percent-encoded `/` or `\` separators before converting them to native absolute paths, so decoding cannot reinterpret URI segment boundaries. ## Testing - Cover uppercase and lowercase encodings for drive colons and path separators, including local-drive and UNC file URIs. GitOrigin-RevId: ecb6c92f4ef1af73f85e57a135abf6bc5bb968dc
This commit is contained in:
1 parent
09609ba414
commit
0d9bb6c34c
2 files changed
+49
-4
No files matched your search
@@ -432,7 +432,8 @@ impl PathUri {
|
|||||||
.path_segments()
|
.path_segments()
|
||||||
.and_then(|mut segments| segments.find(|segment| !segment.is_empty()))
|
.and_then(|mut segments| segments.find(|segment| !segment.is_empty()))
|
||||||
.is_some_and(|segment| {
|
.is_some_and(|segment| {
|
||||||
matches!(segment.as_bytes(), [drive, b':'] if drive.eq_ignore_ascii_case(&path_bytes[0]))
|
is_windows_drive_uri_segment(segment)
|
||||||
|
&& segment.as_bytes()[0].eq_ignore_ascii_case(&path_bytes[0])
|
||||||
});
|
});
|
||||||
if !same_drive {
|
if !same_drive {
|
||||||
return Err(PathUriParseError::InvalidFileUriPath {
|
return Err(PathUriParseError::InvalidFileUriPath {
|
||||||
@@ -497,9 +498,13 @@ impl PathUri {
|
|||||||
/// The URI's inferred path convention must match the current host. Conversion should succeed
|
/// The URI's inferred path convention must match the current host. Conversion should succeed
|
||||||
/// when the URI was created from an [`AbsolutePathBuf`] on the current host, including fallback
|
/// when the URI was created from an [`AbsolutePathBuf`] on the current host, including fallback
|
||||||
/// URIs created by [`Self::from_abs_path`]. Foreign conventions are rejected rather than being
|
/// URIs created by [`Self::from_abs_path`]. Foreign conventions are rejected rather than being
|
||||||
/// projected onto a syntactically valid but unrelated host path.
|
/// projected onto a syntactically valid but unrelated host path. Encoded Windows path
|
||||||
|
/// separators are rejected before native conversion can reinterpret URI segment boundaries.
|
||||||
pub fn to_abs_path(&self) -> io::Result<AbsolutePathBuf> {
|
pub fn to_abs_path(&self) -> io::Result<AbsolutePathBuf> {
|
||||||
if self.infer_path_convention() != Some(PathConvention::native()) {
|
if self.infer_path_convention() != Some(PathConvention::native())
|
||||||
|
|| (PathConvention::native() == PathConvention::Windows
|
||||||
|
&& containment_path_segments(&self.0, PathConvention::Windows).is_none())
|
||||||
|
{
|
||||||
return Err(io::Error::new(
|
return Err(io::Error::new(
|
||||||
io::ErrorKind::InvalidInput,
|
io::ErrorKind::InvalidInput,
|
||||||
PathUriParseError::InvalidFileUriPath {
|
PathUriParseError::InvalidFileUriPath {
|
||||||
@@ -700,7 +705,10 @@ fn decode_bad_path_uri(url: &Url) -> Option<Vec<u8>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_windows_drive_uri_segment(segment: &str) -> bool {
|
fn is_windows_drive_uri_segment(segment: &str) -> bool {
|
||||||
matches!(segment.as_bytes(), [drive, b':'] if drive.is_ascii_alphabetic())
|
matches!(
|
||||||
|
segment.as_bytes(),
|
||||||
|
[drive, b':'] | [drive, b'%', b'3', b'A' | b'a'] if drive.is_ascii_alphabetic()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn containment_path_segments(url: &Url, convention: PathConvention) -> Option<Vec<&str>> {
|
fn containment_path_segments(url: &Url, convention: PathConvention) -> Option<Vec<&str>> {
|
||||||
|
|||||||
@@ -79,6 +79,29 @@ fn non_native_uri_io_conversion_is_invalid_input() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn windows_uri_native_conversion_rejects_encoded_separators() {
|
||||||
|
for uri in [
|
||||||
|
"file:///C%3A/plugins/demo/..%5Coutside.json",
|
||||||
|
"file:///C%3a/plugins/demo/..%5coutside.json",
|
||||||
|
"file:///C:/plugins/demo/..%2Foutside.json",
|
||||||
|
"file://server/share/plugins/demo/..%5Coutside.json",
|
||||||
|
] {
|
||||||
|
let uri = PathUri::parse(uri).expect("valid Windows file URI");
|
||||||
|
|
||||||
|
assert_eq!(uri.infer_path_convention(), Some(PathConvention::Windows));
|
||||||
|
assert!(containment_path_segments(&uri.0, PathConvention::Windows).is_none());
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
assert_eq!(
|
||||||
|
uri.to_abs_path()
|
||||||
|
.expect_err("encoded Windows separators must not reach native conversion")
|
||||||
|
.kind(),
|
||||||
|
io::ErrorKind::InvalidInput
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn file_uri_parses_a_windows_path_on_any_host() {
|
fn file_uri_parses_a_windows_path_on_any_host() {
|
||||||
let uri = PathUri::parse("file:///C:/Users/Alice%20Smith/src/main.rs")
|
let uri = PathUri::parse("file:///C:/Users/Alice%20Smith/src/main.rs")
|
||||||
@@ -142,6 +165,14 @@ fn infers_path_conventions_from_uri_shape() {
|
|||||||
("file:///home/alice/src", Some(PathConvention::Posix)),
|
("file:///home/alice/src", Some(PathConvention::Posix)),
|
||||||
("file:///C:/Users/Alice/src", Some(PathConvention::Windows)),
|
("file:///C:/Users/Alice/src", Some(PathConvention::Windows)),
|
||||||
("file:///d:", Some(PathConvention::Windows)),
|
("file:///d:", Some(PathConvention::Windows)),
|
||||||
|
(
|
||||||
|
"file:///c%3A/Users/Alice/src",
|
||||||
|
Some(PathConvention::Windows),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"file:///D%3a/Users/Alice/src",
|
||||||
|
Some(PathConvention::Windows),
|
||||||
|
),
|
||||||
("file://server/share/src", Some(PathConvention::Windows)),
|
("file://server/share/src", Some(PathConvention::Windows)),
|
||||||
// Opaque fallback for POSIX bytes `/tmp/null-\0-\xff-byte`.
|
// Opaque fallback for POSIX bytes `/tmp/null-\0-\xff-byte`.
|
||||||
(
|
(
|
||||||
@@ -208,6 +239,10 @@ fn inferred_native_path_string_uses_the_inferred_convention() {
|
|||||||
"file:///C:/Users/Alice%20Smith/main.rs",
|
"file:///C:/Users/Alice%20Smith/main.rs",
|
||||||
r"C:\Users\Alice Smith\main.rs",
|
r"C:\Users\Alice Smith\main.rs",
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"file:///c%3A/Users/Alice/src/main.rs",
|
||||||
|
r"C:\Users\Alice\src\main.rs",
|
||||||
|
),
|
||||||
("file://server/share/main.rs", r"\\server\share\main.rs"),
|
("file://server/share/main.rs", r"\\server\share\main.rs"),
|
||||||
("file://server/", "file://server/"),
|
("file://server/", "file://server/"),
|
||||||
("file:///%00/bad/path/YQ", "file:///%00/bad/path/YQ"),
|
("file:///%00/bad/path/YQ", "file:///%00/bad/path/YQ"),
|
||||||
@@ -994,6 +1029,8 @@ fn join_resolves_windows_same_drive_relative_path() {
|
|||||||
for (base, path, expected) in [
|
for (base, path, expected) in [
|
||||||
("file:///C:/base", r"C:tmp", "file:///C:/base/tmp"),
|
("file:///C:/base", r"C:tmp", "file:///C:/base/tmp"),
|
||||||
("file:///C:/base", r"c:tmp", "file:///C:/base/tmp"),
|
("file:///C:/base", r"c:tmp", "file:///C:/base/tmp"),
|
||||||
|
("file:///C%3A/base", r"C:tmp", "file:///C%3A/base/tmp"),
|
||||||
|
("file:///C%3a/base", r"c:tmp", "file:///C%3a/base/tmp"),
|
||||||
("file:///C:/base/dir", r"C:..\tmp", "file:///C:/base/tmp"),
|
("file:///C:/base/dir", r"C:..\tmp", "file:///C:/base/tmp"),
|
||||||
("file:///C:/base", "C:", "file:///C:/base"),
|
("file:///C:/base", "C:", "file:///C:/base"),
|
||||||
] {
|
] {
|
||||||
|
|||||||
Reference in new issue
Block a user