use super::model::PatternEntry;
pub fn is_host_pattern(pattern: &str) -> bool {
pattern.contains('*')
|| pattern.contains('?')
|| pattern.contains('[')
|| pattern.starts_with('!')
|| pattern.contains(' ')
|| pattern.contains('\t')
}
pub fn ssh_pattern_match(pattern: &str, text: &str) -> bool {
if let Some(rest) = pattern.strip_prefix('!') {
return !match_glob(rest, text);
}
match_glob(pattern, text)
}
fn match_glob(pattern: &str, text: &str) -> bool {
if text.is_empty() {
return pattern.is_empty();
}
if pattern.is_empty() {
return false;
}
let pat: Vec<char> = pattern.chars().collect();
let txt: Vec<char> = text.chars().collect();
glob_match(&pat, &txt)
}
fn glob_match(pat: &[char], txt: &[char]) -> bool {
let mut pi = 0;
let mut ti = 0;
let mut star: Option<(usize, usize)> = None;
while ti < txt.len() {
if pi < pat.len() && pat[pi] == '?' {
pi += 1;
ti += 1;
} else if pi < pat.len() && pat[pi] == '*' {
star = Some((pi + 1, ti));
pi += 1;
} else if pi < pat.len() && pat[pi] == '[' {
if let Some((matches, end)) = match_char_class(pat, pi, txt[ti]) {
if matches {
pi = end;
ti += 1;
} else if let Some((spi, sti)) = star {
let sti = sti + 1;
star = Some((spi, sti));
pi = spi;
ti = sti;
} else {
return false;
}
} else if let Some((spi, sti)) = star {
let sti = sti + 1;
star = Some((spi, sti));
pi = spi;
ti = sti;
} else {
return false;
}
} else if pi < pat.len() && pat[pi] == txt[ti] {
pi += 1;
ti += 1;
} else if let Some((spi, sti)) = star {
let sti = sti + 1;
star = Some((spi, sti));
pi = spi;
ti = sti;
} else {
return false;
}
}
while pi < pat.len() && pat[pi] == '*' {
pi += 1;
}
pi == pat.len()
}
fn match_char_class(pat: &[char], start: usize, ch: char) -> Option<(bool, usize)> {
let mut i = start + 1;
if i >= pat.len() {
return None;
}
let negate = pat[i] == '!' || pat[i] == '^';
if negate {
i += 1;
}
let mut matched = false;
while i < pat.len() && pat[i] != ']' {
if i + 2 < pat.len() && pat[i + 1] == '-' && pat[i + 2] != ']' {
let lo = pat[i];
let hi = pat[i + 2];
if ch >= lo && ch <= hi {
matched = true;
}
i += 3;
} else {
matched |= pat[i] == ch;
i += 1;
}
}
if i >= pat.len() {
return None;
}
let result = if negate { !matched } else { matched };
Some((result, i + 1))
}
pub fn host_pattern_matches(host_pattern: &str, alias: &str) -> bool {
let patterns: Vec<&str> = host_pattern.split_whitespace().collect();
if patterns.is_empty() {
return false;
}
let mut any_positive_match = false;
for pat in &patterns {
if let Some(neg) = pat.strip_prefix('!') {
if match_glob(neg, alias) {
return false;
}
} else if ssh_pattern_match(pat, alias) {
any_positive_match = true;
}
}
any_positive_match
}
pub fn proxy_jump_contains_self(proxy_jump: &str, alias: &str) -> bool {
proxy_jump.split(',').any(|hop| {
let h = hop.trim();
let h = h.split_once('@').map_or(h, |(_, host)| host);
let h = if let Some(bracketed) = h.strip_prefix('[') {
bracketed.split_once(']').map_or(h, |(host, _)| host)
} else {
h.rsplit_once(':').map_or(h, |(host, _)| host)
};
h == alias
})
}
pub(super) fn apply_first_match_fields(
proxy_jump: &mut String,
user: &mut String,
identity_file: &mut String,
p: &PatternEntry,
) {
if proxy_jump.is_empty() && !p.proxy_jump.is_empty() {
proxy_jump.clone_from(&p.proxy_jump);
}
if user.is_empty() && !p.user.is_empty() {
user.clone_from(&p.user);
}
if identity_file.is_empty() && !p.identity_file.is_empty() {
identity_file.clone_from(&p.identity_file);
}
}