//! 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, } }