use gix_validate::submodule::name::Error; #[test]fn valid() { fn validate(name: &str) -> Result<(), Error> { gix_validate::submodule::name(name.into()).map(|_| ()) } for valid_name in ["a/./b/..[", "..a/./b/", r"..a\./b\", "你好"] { validate(valid_name).unwrap_or_else(|err| panic!("{valid_name} should be valid: {err:?}")); }} mod invalid { use bstr::ByteSlice; macro_rules! mktest { ($name:ident, $input:literal, $expected:ident) => { #[test] fn $name() { match gix_validate::submodule::name($input.as_bstr()) { Err(gix_validate::submodule::name::Error::$expected) => {} got => panic!("Wanted {}, got {:?}", stringify!($expected), got), } } }; } mktest!(empty, b"", Empty); mktest!(starts_with_parent_component, b"../", ParentComponent); mktest!(parent_component_in_middle, b"hi/../ho", ParentComponent); mktest!(ends_with_parent_component, b"hi/ho/..", ParentComponent); mktest!(only_parent_component, b"..", ParentComponent); mktest!(starts_with_parent_component_backslash, br"..\", ParentComponent); mktest!(parent_component_in_middle_backslash, br"hi\..\ho", ParentComponent); mktest!(ends_with_parent_component_backslash, br"hi\ho\..", ParentComponent); /// Reproducer for GHSA-p3hw-mv63-rf9w: a crafted submodule name can place a harmless `..` /// first and a real `../` traversal later, so validators that only inspect the first match /// accept a name that still escapes `.git/modules`. #[test] fn traversal_after_a_benign_double_dot_is_rejected() { match gix_validate::submodule::name(b"a..b/../../../.git/".as_bstr()) { Err(gix_validate::submodule::name::Error::ParentComponent) => {} got => panic!("Wanted ParentComponent, got {got:?}"), } } /// Reproducer for GHSA-p3hw-mv63-rf9w: the same first-match bypass also applies to /// Windows-style separators. #[test] fn backslash_traversal_after_a_benign_double_dot_is_rejected() { match gix_validate::submodule::name(br"a..b\..\..\..\.git\".as_bstr()) { Err(gix_validate::submodule::name::Error::ParentComponent) => {} got => panic!("Wanted ParentComponent, got {got:?}"), } }}
use gix_validate::submodule::name::Error;
#[test]
fn valid() {
fn validate(name: &str) -> Result<(), Error> {
gix_validate::submodule::name(name.into()).map(|_| ())
}
for valid_name in ["a/./b/..[", "..a/./b/", r"..a\./b\", "你好"] {
validate(valid_name).unwrap_or_else(|err| panic!("{valid_name} should be valid: {err:?}"));
}
}
mod invalid {
use bstr::ByteSlice;
macro_rules! mktest {
($name:ident, $input:literal, $expected:ident) => {
#[test]
fn $name() {
match gix_validate::submodule::name($input.as_bstr()) {
Err(gix_validate::submodule::name::Error::$expected) => {}
got => panic!("Wanted {}, got {:?}", stringify!($expected), got),
}
}
};
}
mktest!(empty, b"", Empty);
mktest!(starts_with_parent_component, b"../", ParentComponent);
mktest!(parent_component_in_middle, b"hi/../ho", ParentComponent);
mktest!(ends_with_parent_component, b"hi/ho/..", ParentComponent);
mktest!(only_parent_component, b"..", ParentComponent);
mktest!(starts_with_parent_component_backslash, br"..\", ParentComponent);
mktest!(parent_component_in_middle_backslash, br"hi\..\ho", ParentComponent);
mktest!(ends_with_parent_component_backslash, br"hi\ho\..", ParentComponent);
/// Reproducer for GHSA-p3hw-mv63-rf9w: a crafted submodule name can place a harmless `..`
/// first and a real `../` traversal later, so validators that only inspect the first match
/// accept a name that still escapes `.git/modules`.
#[test]
fn traversal_after_a_benign_double_dot_is_rejected() {
match gix_validate::submodule::name(b"a..b/../../../.git/".as_bstr()) {
Err(gix_validate::submodule::name::Error::ParentComponent) => {}
got => panic!("Wanted ParentComponent, got {got:?}"),
}
}
/// Reproducer for GHSA-p3hw-mv63-rf9w: the same first-match bypass also applies to
/// Windows-style separators.
#[test]
fn backslash_traversal_after_a_benign_double_dot_is_rejected() {
match gix_validate::submodule::name(br"a..b\..\..\..\.git\".as_bstr()) {
Err(gix_validate::submodule::name::Error::ParentComponent) => {}
got => panic!("Wanted ParentComponent, got {got:?}"),
}
}
}