use crate::HttpRequest;
use http::uri::{InvalidUri, Uri};
use jsonrpsee_core::http_helpers;
#[derive(Clone, Hash, PartialEq, Eq, Debug)]
pub struct Authority {
pub host: String,
pub port: Port,
}
#[derive(Debug, thiserror::Error)]
pub enum AuthorityError {
#[error(transparent)]
InvalidUri(InvalidUri),
#[error("Invalid port: {0}")]
InvalidPort(String),
#[error("The host was not found")]
MissingHost,
}
#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
pub enum Port {
Default,
Any,
Fixed(u16),
}
impl Authority {
fn inner_from_str(value: &str) -> Result<Self, AuthorityError> {
let uri: Uri = value.parse().map_err(AuthorityError::InvalidUri)?;
let authority = uri.authority().ok_or(AuthorityError::MissingHost)?;
let host = authority.host();
let maybe_port = &authority.as_str()[host.len()..];
let port = match maybe_port.split_once(':') {
Some((_, "*")) => Port::Any,
Some((_, p)) => {
let port_u16: u16 =
p.parse().map_err(|e: std::num::ParseIntError| AuthorityError::InvalidPort(e.to_string()))?;
match default_port(uri.scheme_str()) {
Some(p) if p == port_u16 => Port::Default,
_ => port_u16.into(),
}
}
None => Port::Default,
};
Ok(Self { host: host.to_owned(), port })
}
pub fn from_http_request<T>(request: &HttpRequest<T>) -> Option<Self> {
let host_header =
http_helpers::read_header_value(request.headers(), hyper::header::HOST).map(Authority::try_from);
let uri = request.uri().authority().map(|v| Authority::try_from(v.as_str()));
match (host_header, uri) {
(Some(Ok(a1)), Some(Ok(a2))) => {
if a1 == a2 {
Some(a1)
} else {
None
}
}
(Some(Ok(a)), _) => Some(a),
(_, Some(Ok(a))) => Some(a),
_ => None,
}
}
}
impl<'a> TryFrom<&'a str> for Authority {
type Error = AuthorityError;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
Self::inner_from_str(value)
}
}
impl TryFrom<String> for Authority {
type Error = AuthorityError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::inner_from_str(&value)
}
}
impl TryFrom<std::net::SocketAddr> for Authority {
type Error = AuthorityError;
fn try_from(sockaddr: std::net::SocketAddr) -> Result<Self, Self::Error> {
Self::inner_from_str(&sockaddr.to_string())
}
}
impl From<u16> for Port {
fn from(port: u16) -> Port {
Port::Fixed(port)
}
}
fn default_port(scheme: Option<&str>) -> Option<u16> {
match scheme {
Some("http") | Some("ws") => Some(80),
Some("https") | Some("wss") => Some(443),
Some("ftp") => Some(21),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::{Authority, HttpRequest, Port};
use hyper::header::HOST;
fn authority(host: &str, port: Port) -> Authority {
Authority { host: host.to_owned(), port }
}
type EmptyBody = http_body_util::Empty<hyper::body::Bytes>;
#[test]
fn should_parse_valid_authority() {
assert_eq!(Authority::try_from("http://parity.io").unwrap(), authority("parity.io", Port::Default));
assert_eq!(Authority::try_from("https://parity.io:8443").unwrap(), authority("parity.io", Port::Fixed(8443)));
assert_eq!(Authority::try_from("chrome-extension://124.0.0.1").unwrap(), authority("124.0.0.1", Port::Default));
assert_eq!(Authority::try_from("http://*.domain:*/somepath").unwrap(), authority("*.domain", Port::Any));
assert_eq!(Authority::try_from("parity.io").unwrap(), authority("parity.io", Port::Default));
assert_eq!(Authority::try_from("127.0.0.1:8845").unwrap(), authority("127.0.0.1", Port::Fixed(8845)));
assert_eq!(
Authority::try_from("http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]:9933/").unwrap(),
authority("[2001:db8:85a3:8d3:1319:8a2e:370:7348]", Port::Fixed(9933))
);
assert_eq!(
Authority::try_from("http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]/").unwrap(),
authority("[2001:db8:85a3:8d3:1319:8a2e:370:7348]", Port::Default)
);
assert_eq!(
Authority::try_from("https://user:password@example.com/tmp/foo").unwrap(),
authority("example.com", Port::Default)
);
}
#[test]
fn should_not_parse_invalid_authority() {
assert!(Authority::try_from("/foo/bar").is_err());
assert!(Authority::try_from("user:password").is_err());
assert!(Authority::try_from("parity.io/somepath").is_err());
assert!(Authority::try_from("127.0.0.1:8545/somepath").is_err());
assert!(Authority::try_from("127.0.0.1:-1337").is_err());
}
#[test]
fn authority_from_http_only_host_works() {
let req = HttpRequest::builder().header(HOST, "example.com").body(EmptyBody::new()).unwrap();
assert!(Authority::from_http_request(&req).is_some());
}
#[test]
fn authority_only_uri_works() {
let req = HttpRequest::builder().uri("example.com").body(EmptyBody::new()).unwrap();
assert!(Authority::from_http_request(&req).is_some());
}
#[test]
fn authority_host_and_uri_works() {
let req = HttpRequest::builder()
.header(HOST, "example.com:9999")
.uri("example.com:9999")
.body(EmptyBody::new())
.unwrap();
assert!(Authority::from_http_request(&req).is_some());
}
#[test]
fn authority_host_and_uri_mismatch() {
let req =
HttpRequest::builder().header(HOST, "example.com:9999").uri("example.com").body(EmptyBody::new()).unwrap();
assert!(Authority::from_http_request(&req).is_none());
}
#[test]
fn authority_missing_host_and_uri() {
let req = HttpRequest::builder().body(EmptyBody::new()).unwrap();
assert!(Authority::from_http_request(&req).is_none());
}
}