///|
fn lower_ascii(value : String) -> String {
let output = StringBuilder()
for char in value {
output.write_char(char.to_ascii_lowercase())
}
output.to_string()
}
///|
fn glob_from(
pattern : String,
pattern_index : Int,
value : String,
value_index : Int,
) -> Bool {
let mut pattern_pos = pattern_index
let mut value_pos = value_index
let mut last_star = -1
let mut retry_value_pos = value_index
while value_pos < value.length() {
if pattern_pos < pattern.length() &&
(pattern[pattern_pos] == '?' || pattern[pattern_pos] == value[value_pos]) {
pattern_pos += 1
value_pos += 1
} else if pattern_pos < pattern.length() && pattern[pattern_pos] == '*' {
last_star = pattern_pos
pattern_pos += 1
retry_value_pos = value_pos
} else if last_star >= 0 {
retry_value_pos += 1
value_pos = retry_value_pos
pattern_pos = last_star + 1
} else {
return false
}
}
while pattern_pos < pattern.length() && pattern[pattern_pos] == '*' {
pattern_pos += 1
}
pattern_pos == pattern.length()
}
///|
/// Maximum accepted pattern length for the bounded public matching APIs.
pub let default_pattern_length_limit : Int = 4 * 1024
///|
/// Maximum number of entries accepted by one compiled pattern-list.
pub let default_pattern_list_limit : Int = 256
///|
/// Maximum accepted host or Match-context value length.
pub let default_match_value_length_limit : Int = 64 * 1024
///|
/// Match one case-insensitive OpenSSH host glob containing `*` and `?`.
///
/// This P0 matcher folds ASCII only. Inputs exceeding
/// `default_pattern_length_limit` (or the value limit) are rejected as a
/// non-match so that the compatibility Bool API remains allocation-free and
/// non-throwing.
pub fn host_matches(pattern : String, host : String) -> Bool {
if pattern.length() == 0 ||
pattern == "!" ||
pattern.length() > default_pattern_length_limit ||
host.length() > default_match_value_length_limit {
false
} else {
glob_from(lower_ascii(pattern), 0, lower_ascii(host), 0)
}
}
///|
/// Match an OpenSSH pattern-list. A matching negated item always excludes the
/// host; otherwise at least one positive item must match.
pub fn patterns_match(patterns : Array[String], host : String) -> Bool {
if patterns.length() > default_pattern_list_limit ||
host.length() > default_match_value_length_limit {
return false
}
let positive_match = Ref(false)
let normalized_host = lower_ascii(host)
for item in patterns {
if item.length() == 0 ||
item == "!" ||
item.length() > default_pattern_length_limit {
return false
}
let normalized_pattern = lower_ascii(item)
if item.length() > 0 && item[0] == '!' {
if glob_from(normalized_pattern, 1, normalized_host, 0) {
return false
}
} else if glob_from(normalized_pattern, 0, normalized_host, 0) {
positive_match.val = true
}
}
positive_match.val
}