Skip to content

Commit f5ef10d

Browse files
committed
Test Windows <reserved>.middle.extension filenames
When `<reserved>` is a reserved device name on Windows like `NUL`, in addition to filenames where it is followed by an extension being treated specially in the same way (`<reserved>.extension`), filenames where the operation of repeatedly removing extensions would produce `<reserved>` (`<reserved>.middle.extension`, `<reserved>.middle1.middle2.extension`, and so forth) are also treated by Windows like the reserved name. The existing code in `gix-validate`, and thus the validation of paths and refs that checks for reserved Windows device names, already does the right thing here. While the existing tests are presented as being for extensions, the implementation treats a name as reserved when it is parsed as a reserved name followed by a `.` (among other cases), regardless of whether what comes after the `.` is actually an extension in the strict sense. Therefore this is just adding regression tests to express the rule more clearly and, more importantly, to protect against accidental breakage in future changes. (Because of the way gitoxide uses this validation, suhc breakage would likely introduce a vulnerability similar to CVE-2024-35197, because this is part of how we safeguard against malicious names when cloning untrusted remote repos.) The idea to test this is inspired by a new check in `dunce`: https://gitlab.com/kornelski/dunce/-/commit/0ceb0ae141bf78c6d9d68488a55d22cd94658339
1 parent 6daaba3 commit f5ef10d

File tree

1 file changed

+7
-2
lines changed

1 file changed

+7
-2
lines changed

gix-validate/tests/path/mod.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
#[test]
22
fn component_is_windows_device() {
3-
for device in ["con", "CONIN$", "lpt1.txt", "AUX", "Prn", "NUL", "COM9"] {
3+
for device in ["con", "CONIN$", "lpt1.txt", "AUX", "Prn", "NUL", "COM9", "nul.a.b "] {
44
assert!(gix_validate::path::component_is_windows_device(device.into()));
55
}
6-
for not_device in ["coni", "CONIN", "lpt", "AUXi", "aPrn", "NULl", "COM"] {
6+
for not_device in ["coni", "CONIN", "lpt", "AUXi", "aPrn", "NULl", "COM", "a.nul.b "] {
77
assert!(!gix_validate::path::component_is_windows_device(not_device.into()));
88
}
99
}
@@ -82,6 +82,9 @@ mod component {
8282
mktest!(conin_without_dollar, b"conin");
8383
mktest!(not_con, b"com");
8484
mktest!(also_not_con, b"co");
85+
mktest!(con_as_middle, b"x.CON.zip");
86+
mktest!(con_after_space, b" CON");
87+
mktest!(con_after_space_mixed, b" coN.tar.xz");
8588
mktest!(not_nul, b"null");
8689
mktest!(
8790
not_dot_gitmodules_shorter_hfs,
@@ -248,6 +251,8 @@ mod component {
248251
mktest!(prn_mixed_with_extension, b"PrN.abc", Error::WindowsReservedName);
249252
mktest!(con, b"CON", Error::WindowsReservedName);
250253
mktest!(con_with_extension, b"CON.abc", Error::WindowsReservedName);
254+
mktest!(con_with_middle, b"CON.tar.xz", Error::WindowsReservedName);
255+
mktest!(con_mixed_with_middle, b"coN.tar.xz ", Error::WindowsReservedName);
251256
mktest!(
252257
conout_mixed_with_extension,
253258
b"ConOut$ .xyz",

0 commit comments

Comments
 (0)