use plane_common::types::{BearerToken, ClusterName};
use plane_dynamic_proxy::hyper::http::uri::{self, PathAndQuery};
use std::str::FromStr;
const HTTPS_PORT_SUFFIX: &str = ":443";
pub fn subdomain_from_host<'a>(
host: &'a str,
cluster: &ClusterName,
) -> Result<Option<&'a str>, ()> {
let host = if let Some(host) = host.strip_suffix(HTTPS_PORT_SUFFIX) {
host
} else {
host
};
if let Some(subdomain) = host.strip_suffix(cluster.as_str()) {
if subdomain.is_empty() {
Ok(None)
} else if let Some(subdomain) = subdomain.strip_suffix('.') {
Ok(Some(subdomain))
} else {
Err(())
}
} else {
tracing::warn!(host, "Host header does not end in cluster name.");
Err(())
}
}
pub fn get_and_maybe_remove_bearer_token(parts: &mut uri::Parts) -> Option<BearerToken> {
let path_and_query = parts.path_and_query.clone()?;
let full_path = path_and_query.path().strip_prefix('/')?;
let (token, path) = match full_path.split_once('/') {
Some((token, path)) => (token, path),
None => (full_path, ""),
};
if token.is_empty() {
return None;
}
let token = BearerToken::from(token.to_string());
if token.is_static() {
return Some(token);
}
let query = path_and_query
.query()
.map(|query| format!("?{}", query))
.unwrap_or_default();
parts.path_and_query = Some(
PathAndQuery::from_str(format!("/{}{}", path, query).as_str())
.expect("Path and query is valid."),
);
Some(token)
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
use uri::Uri;
#[test]
fn no_subdomains() {
let host = "foo.bar.baz";
let cluster = ClusterName::from_str("foo.bar.baz").unwrap();
assert_eq!(subdomain_from_host(host, &cluster), Ok(None));
}
#[test]
fn valid_subdomain() {
let host = "foobar.example.com";
let cluster = ClusterName::from_str("example.com").unwrap();
assert_eq!(subdomain_from_host(host, &cluster), Ok(Some("foobar")));
}
#[test]
fn valid_suffix_no_dot() {
let host = "foobarexample.com";
let cluster = ClusterName::from_str("example.com").unwrap();
assert_eq!(subdomain_from_host(host, &cluster), Err(()));
}
#[test]
fn invalid_suffix() {
let host = "abc.abc.com";
let cluster = ClusterName::from_str("example.com").unwrap();
assert_eq!(subdomain_from_host(host, &cluster), Err(()));
}
#[test]
fn allowed_port() {
let host = "foobar.myhost:8080";
let cluster = ClusterName::from_str("myhost:8080").unwrap();
assert_eq!(subdomain_from_host(host, &cluster), Ok(Some("foobar")));
}
#[test]
fn port_required() {
let host = "foobar.myhost";
let cluster = ClusterName::from_str("myhost:8080").unwrap();
assert_eq!(subdomain_from_host(host, &cluster), Err(()));
}
#[test]
fn port_must_match() {
let host = "foobar.myhost:8080";
let cluster = ClusterName::from_str("myhost").unwrap();
assert_eq!(subdomain_from_host(host, &cluster), Err(()));
}
#[test]
fn port_443_optional() {
let host = "foobar.myhost:443";
let cluster = ClusterName::from_str("myhost").unwrap();
assert_eq!(subdomain_from_host(host, &cluster), Ok(Some("foobar")));
}
#[test]
fn test_get_and_maybe_remove_bearer_token() {
let url = Uri::from_str("https://example.com/foo/bar").unwrap();
let mut parts = url.into_parts();
assert_eq!(
get_and_maybe_remove_bearer_token(&mut parts),
Some(BearerToken::from("foo".to_string()))
);
assert_eq!(
parts.path_and_query,
Some(PathAndQuery::from_str("/bar").unwrap())
);
}
#[test]
fn test_get_and_maybe_remove_bearer_token_ends_no_slash() {
let url = Uri::from_str("https://example.com/foo").unwrap();
let mut parts = url.into_parts();
assert_eq!(
get_and_maybe_remove_bearer_token(&mut parts),
Some(BearerToken::from("foo".to_string()))
);
assert_eq!(
parts.path_and_query,
Some(PathAndQuery::from_str("/").unwrap())
);
}
#[test]
fn test_get_and_maybe_remove_bearer_token_ends_in_slash() {
let url = Uri::from_str("https://example.com/foo/").unwrap();
let mut parts = url.into_parts();
assert_eq!(
get_and_maybe_remove_bearer_token(&mut parts),
Some(BearerToken::from("foo".to_string()))
);
assert_eq!(
parts.path_and_query,
Some(PathAndQuery::from_str("/").unwrap())
);
}
#[test]
fn test_get_and_maybe_remove_bearer_token_no_token() {
let url = Uri::from_str("https://example.com/").unwrap();
let mut parts = url.into_parts();
assert_eq!(get_and_maybe_remove_bearer_token(&mut parts), None);
assert_eq!(
parts.path_and_query,
Some(PathAndQuery::from_str("/").unwrap())
);
}
#[test]
fn test_get_and_maybe_remove_bearer_token_static_token() {
let url = Uri::from_str("https://example.com/s.foo/bar").unwrap();
let mut parts = url.into_parts();
assert_eq!(
get_and_maybe_remove_bearer_token(&mut parts),
Some(BearerToken::from("s.foo".to_string()))
);
assert_eq!(
parts.path_and_query,
Some(PathAndQuery::from_str("/s.foo/bar").unwrap())
);
}
}