use crate::Error;
pub fn canonicalize_protocol(value: &str) -> Result<String, Error> {
if value.is_empty() {
return Ok(String::new());
}
url::Url::parse(&format!("{}://dummy.test", value))
.map(|url| url.scheme().to_owned())
.map_err(Error::Url)
}
pub fn canonicalize_username(value: &str) -> Result<String, Error> {
if value.is_empty() {
return Ok(String::new());
}
let mut url = url::Url::parse("http://dummy.test").unwrap();
url.set_username(value).unwrap();
Ok(url.username().to_string())
}
pub fn canonicalize_password(value: &str) -> Result<String, Error> {
if value.is_empty() {
return Ok(String::new());
}
let mut url = url::Url::parse("http://dummy.test").unwrap();
url.set_password(Some(value)).unwrap();
Ok(url.password().unwrap().to_string())
}
pub fn canonicalize_hostname(value: &str) -> Result<String, Error> {
if value.is_empty() {
return Ok(String::new());
}
let mut url = url::Url::parse("http://dummy.test").unwrap();
url.set_host(Some(value)).map_err(Error::Url)?;
Ok(url::quirks::hostname(&url).to_string())
}
pub fn canonicalize_ipv6_hostname(value: &str) -> Result<String, Error> {
let valid_ipv6 = value
.chars()
.all(|c| c.is_ascii_hexdigit() || matches!(c, '[' | ']' | ':'));
if !valid_ipv6 {
Err(Error::Url(url::ParseError::InvalidIpv6Address))
} else {
Ok(value.to_ascii_lowercase())
}
}
pub fn canonicalize_port(
value: &str,
mut protocol: Option<&str>,
) -> Result<String, Error> {
if value.is_empty() {
return Ok(String::new());
}
if let Some("") = protocol {
protocol = None;
}
let port = value
.parse::<u16>()
.map_err(|_| Error::Url(url::ParseError::InvalidPort))?;
let mut url =
url::Url::parse(&format!("{}://dummy.test", protocol.unwrap_or("dummy")))
.unwrap();
url.set_port(Some(port)).unwrap(); Ok(url::quirks::port(&url).to_string())
}
pub fn canonicalize_pathname(value: &str) -> Result<String, Error> {
if value.is_empty() {
return Ok(String::new());
}
let leading_slash = value.starts_with('/');
let modified_value = if !leading_slash {
format!("/-{}", value)
} else {
value.to_string()
};
let mut url = url::Url::parse("http://dummy.test").unwrap();
url.set_path(&modified_value);
let mut pathname = url::quirks::pathname(&url);
if !leading_slash {
pathname = &pathname[2..];
}
Ok(pathname.to_string())
}
pub fn canonicalize_an_opaque_pathname(value: &str) -> Result<String, Error> {
if value.is_empty() {
return Ok(String::new());
}
let mut url = url::Url::parse("data:dummy,test").unwrap();
url.set_path(value);
Ok(url::quirks::pathname(&url).to_string())
}
pub fn canonicalize_search(value: &str) -> Result<String, Error> {
if value.is_empty() {
return Ok(String::new());
}
let mut url = url::Url::parse("http://dummy.test").unwrap();
url.set_query(Some(value));
Ok(url.query().unwrap_or("").to_string())
}
pub fn canonicalize_hash(value: &str) -> Result<String, Error> {
if value.is_empty() {
return Ok(String::new());
}
let mut url = url::Url::parse("http://dummy.test").unwrap();
url.set_fragment(Some(value));
Ok(url.fragment().unwrap_or("").to_string())
}
#[derive(Debug, Eq, PartialEq)]
pub enum ProcessType {
Pattern,
Url,
}
pub fn process_protocol_init(
value: &str,
kind: &ProcessType,
) -> Result<String, Error> {
let stripped_value = value.strip_suffix(':').unwrap_or(value);
if kind == &ProcessType::Pattern {
Ok(stripped_value.to_string())
} else {
canonicalize_protocol(stripped_value)
}
}
pub fn process_username_init(
value: &str,
kind: &ProcessType,
) -> Result<String, Error> {
if kind == &ProcessType::Pattern {
Ok(value.to_string())
} else {
canonicalize_username(value)
}
}
pub fn process_password_init(
value: &str,
kind: &ProcessType,
) -> Result<String, Error> {
if kind == &ProcessType::Pattern {
Ok(value.to_string())
} else {
canonicalize_password(value)
}
}
pub fn process_hostname_init(
value: &str,
kind: &ProcessType,
) -> Result<String, Error> {
if kind == &ProcessType::Pattern {
Ok(value.to_string())
} else {
canonicalize_hostname(value)
}
}
pub fn process_port_init(
port_value: &str,
protocol_value: Option<&str>,
kind: &ProcessType,
) -> Result<String, Error> {
if kind == &ProcessType::Pattern {
Ok(port_value.to_string())
} else {
canonicalize_port(port_value, protocol_value)
}
}
pub fn process_pathname_init(
pathname_value: &str,
protocol_value: Option<&str>,
kind: &ProcessType,
) -> Result<String, Error> {
if kind == &ProcessType::Pattern {
Ok(pathname_value.to_string())
} else {
match protocol_value {
Some(protocol) if protocol.is_empty() || is_special_scheme(protocol) => {
canonicalize_pathname(pathname_value)
}
_ => canonicalize_an_opaque_pathname(pathname_value),
}
}
}
pub fn process_search_init(
value: &str,
kind: &ProcessType,
) -> Result<String, Error> {
let stripped_value = if value.starts_with('?') {
value.get(1..).unwrap()
} else {
value
};
if kind == &ProcessType::Pattern {
Ok(stripped_value.to_string())
} else {
canonicalize_search(stripped_value)
}
}
pub fn process_hash_init(
value: &str,
kind: &ProcessType,
) -> Result<String, Error> {
let stripped_value = if value.starts_with('#') {
value.get(1..).unwrap()
} else {
value
};
if kind == &ProcessType::Pattern {
Ok(stripped_value.to_string())
} else {
canonicalize_hash(stripped_value)
}
}
pub fn is_special_scheme(scheme: &str) -> bool {
matches!(scheme, "http" | "https" | "ws" | "wss" | "ftp" | "file")
}
pub fn special_scheme_default_port(scheme: &str) -> Option<&'static str> {
match scheme {
"http" => Some("80"),
"https" => Some("443"),
"ws" => Some("80"),
"wss" => Some("443"),
"ftp" => Some("21"),
"file" => None,
_ => None,
}
}