62 lines
2.0 KiB
Rust
62 lines
2.0 KiB
Rust
//! Host names and patterns, shared by `brokerd` and the egress proxy so both check hosts with the
|
|
//! same rules.
|
|
|
|
/// A host label is 1 to 63 bytes of `a-z`, `0-9` or `-`, and neither starts nor ends with `-`.
|
|
fn valid_label(label: &str) -> bool {
|
|
if !(1..=63).contains(&label.len()) {
|
|
return false;
|
|
}
|
|
let bytes = label.as_bytes();
|
|
if bytes.iter().next() == Some(&b'-') || bytes.iter().last() == Some(&b'-') {
|
|
return false;
|
|
}
|
|
bytes
|
|
.iter()
|
|
.all(|&byte| matches!(byte, b'a'..=b'z' | b'0'..=b'9' | b'-'))
|
|
}
|
|
|
|
/// A host is 1 to 253 bytes of dot-separated labels, each a whole non-dot name, and the last label
|
|
/// starts with a letter. That last rule keeps out every spelling of an IPv4 address.
|
|
pub fn valid_host(host: &str) -> bool {
|
|
if !(1..=253).contains(&host.len()) {
|
|
return false;
|
|
}
|
|
let labels: Vec<&str> = host.split('.').collect();
|
|
if labels.len() < 2 {
|
|
return false;
|
|
}
|
|
for label in &labels {
|
|
if !valid_label(label) {
|
|
return false;
|
|
}
|
|
}
|
|
match labels.iter().last() {
|
|
Some(last) => matches!(last.bytes().next(), Some(byte) if byte.is_ascii_lowercase()),
|
|
None => false,
|
|
}
|
|
}
|
|
|
|
/// A host, or `*.` followed by a host. Nothing else.
|
|
pub fn valid_host_pattern(pattern: &str) -> bool {
|
|
match pattern.strip_prefix("*.") {
|
|
Some(base) => valid_host(base),
|
|
None => valid_host(pattern),
|
|
}
|
|
}
|
|
|
|
/// Does `pattern` match `host`? Without `*.` the strings must be equal; with `*.base` the host must
|
|
/// end in `.base` with something before the dot.
|
|
pub fn host_matches(pattern: &str, host: &str) -> bool {
|
|
let base = match pattern.strip_prefix("*.") {
|
|
Some(base) => base,
|
|
None => return pattern == host,
|
|
};
|
|
match host.strip_suffix(base) {
|
|
Some(before) => match before.strip_suffix('.') {
|
|
Some(prefix) => !prefix.is_empty(),
|
|
None => false,
|
|
},
|
|
None => false,
|
|
}
|
|
}
|