use tungstenite::handshake::server::Request;
use super::AcceptorSettings;
pub(super) fn origin_admitted(
origin: &str,
request: &Request,
settings: &AcceptorSettings,
) -> bool {
if settings
.allowed_origins
.iter()
.any(|allowed| allowed == origin)
{
return true;
}
if settings.allowed_origin_ports.is_empty() {
return false;
}
let Some((origin_host, origin_port)) = host_and_port(origin) else {
return false;
};
let mut hosts = request.headers().get_all("Host").iter();
let (Some(host), None) = (hosts.next(), hosts.next()) else {
return false;
};
let Ok(host) = host.to_str() else {
return false;
};
let request_host = without_port(host);
settings.allowed_origin_ports.contains(&origin_port)
&& origin_host.eq_ignore_ascii_case(request_host)
}
fn host_and_port(origin: &str) -> Option<(&str, u16)> {
let (scheme, rest) = origin.split_once("://")?;
let own_port = match scheme {
"http" => 80,
"https" => 443,
_ => return None,
};
if rest.is_empty() || rest.contains('/') {
return None;
}
let host = without_port(rest);
if host.is_empty() {
return None;
}
let port = if host.len() == rest.len() {
own_port
} else {
rest[host.len() + 1..].parse().ok()?
};
Some((host, port))
}
fn without_port(authority: &str) -> &str {
if authority.starts_with('[') {
return authority
.find(']')
.map_or(authority, |end| &authority[..=end]);
}
match authority.rsplit_once(':') {
Some((host, port)) if !host.contains(':') && port.bytes().all(|b| b.is_ascii_digit()) => {
host
}
_ => authority,
}
}