#[cfg(all(feature = "alloc", feature = "std"))]
use alloc::borrow::Cow;
#[cfg(feature = "alloc")]
use alloc::string::{String, ToString};
use core::hash::{Hash, Hasher};
use core::{fmt, mem, str};
#[cfg(feature = "std")]
use std::error::Error as StdError;
#[non_exhaustive]
#[derive(Clone, Eq, Hash, PartialEq)]
pub enum ServerName<'a> {
DnsName(DnsName<'a>),
IpAddress(IpAddr),
}
impl<'a> ServerName<'a> {
#[cfg(feature = "alloc")]
pub fn to_owned(&self) -> ServerName<'static> {
match self {
Self::DnsName(d) => ServerName::DnsName(d.to_owned()),
Self::IpAddress(i) => ServerName::IpAddress(*i),
}
}
#[cfg(feature = "std")]
pub fn to_str(&self) -> Cow<'_, str> {
match self {
Self::DnsName(d) => d.as_ref().into(),
Self::IpAddress(i) => std::net::IpAddr::from(*i).to_string().into(),
}
}
}
impl<'a> fmt::Debug for ServerName<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::DnsName(d) => f.debug_tuple("DnsName").field(&d.as_ref()).finish(),
Self::IpAddress(i) => f.debug_tuple("IpAddress").field(i).finish(),
}
}
}
#[cfg(feature = "alloc")]
impl TryFrom<String> for ServerName<'static> {
type Error = InvalidDnsNameError;
fn try_from(value: String) -> Result<Self, Self::Error> {
match DnsName::try_from_string(value) {
Ok(dns) => Ok(Self::DnsName(dns)),
Err(value) => match IpAddr::try_from(value.as_str()) {
Ok(ip) => Ok(Self::IpAddress(ip)),
Err(_) => Err(InvalidDnsNameError),
},
}
}
}
impl<'a> TryFrom<&'a [u8]> for ServerName<'a> {
type Error = InvalidDnsNameError;
fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
match str::from_utf8(value) {
Ok(s) => Self::try_from(s),
Err(_) => Err(InvalidDnsNameError),
}
}
}
impl<'a> TryFrom<&'a str> for ServerName<'a> {
type Error = InvalidDnsNameError;
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
match DnsName::try_from(s) {
Ok(dns) => Ok(Self::DnsName(dns)),
Err(InvalidDnsNameError) => match IpAddr::try_from(s) {
Ok(ip) => Ok(Self::IpAddress(ip)),
Err(_) => Err(InvalidDnsNameError),
},
}
}
}
impl From<IpAddr> for ServerName<'_> {
fn from(addr: IpAddr) -> Self {
Self::IpAddress(addr)
}
}
#[cfg(feature = "std")]
impl From<std::net::IpAddr> for ServerName<'_> {
fn from(addr: std::net::IpAddr) -> Self {
Self::IpAddress(addr.into())
}
}
impl From<Ipv4Addr> for ServerName<'_> {
fn from(v4: Ipv4Addr) -> Self {
Self::IpAddress(IpAddr::V4(v4))
}
}
impl From<Ipv6Addr> for ServerName<'_> {
fn from(v6: Ipv6Addr) -> Self {
Self::IpAddress(IpAddr::V6(v6))
}
}
#[cfg(feature = "std")]
impl From<std::net::Ipv4Addr> for ServerName<'_> {
fn from(v4: std::net::Ipv4Addr) -> Self {
Self::IpAddress(IpAddr::V4(v4.into()))
}
}
#[cfg(feature = "std")]
impl From<std::net::Ipv6Addr> for ServerName<'_> {
fn from(v6: std::net::Ipv6Addr) -> Self {
Self::IpAddress(IpAddr::V6(v6.into()))
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct DnsName<'a>(DnsNameInner<'a>);
impl<'a> DnsName<'a> {
pub fn borrow(&'a self) -> Self {
Self(match self {
Self(DnsNameInner::Borrowed(s)) => DnsNameInner::Borrowed(s),
#[cfg(feature = "alloc")]
Self(DnsNameInner::Owned(s)) => DnsNameInner::Borrowed(s.as_str()),
})
}
#[cfg(feature = "alloc")]
pub fn to_lowercase_owned(&self) -> DnsName<'static> {
DnsName(DnsNameInner::Owned(self.as_ref().to_ascii_lowercase()))
}
#[cfg(feature = "alloc")]
pub fn to_owned(&self) -> DnsName<'static> {
DnsName(DnsNameInner::Owned(match self {
Self(DnsNameInner::Borrowed(s)) => s.to_string(),
#[cfg(feature = "alloc")]
Self(DnsNameInner::Owned(s)) => s.clone(),
}))
}
#[cfg(feature = "alloc")]
fn try_from_string(s: String) -> Result<Self, String> {
match validate(s.as_bytes()) {
Ok(_) => Ok(Self(DnsNameInner::Owned(s))),
Err(_) => Err(s),
}
}
}
#[cfg(feature = "alloc")]
impl TryFrom<String> for DnsName<'static> {
type Error = InvalidDnsNameError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::try_from_string(value).map_err(|_| InvalidDnsNameError)
}
}
impl<'a> TryFrom<&'a str> for DnsName<'a> {
type Error = InvalidDnsNameError;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
validate(value.as_bytes())?;
Ok(Self(DnsNameInner::Borrowed(value)))
}
}
impl<'a> TryFrom<&'a [u8]> for DnsName<'a> {
type Error = InvalidDnsNameError;
fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
validate(value)?;
Ok(Self(DnsNameInner::Borrowed(str::from_utf8(value).unwrap())))
}
}
impl<'a> AsRef<str> for DnsName<'a> {
fn as_ref(&self) -> &str {
match self {
Self(DnsNameInner::Borrowed(s)) => s,
#[cfg(feature = "alloc")]
Self(DnsNameInner::Owned(s)) => s.as_str(),
}
}
}
#[derive(Clone, Eq)]
enum DnsNameInner<'a> {
Borrowed(&'a str),
#[cfg(feature = "alloc")]
Owned(String),
}
impl<'a> PartialEq<Self> for DnsNameInner<'a> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Borrowed(s), Self::Borrowed(o)) => s.eq_ignore_ascii_case(o),
#[cfg(feature = "alloc")]
(Self::Borrowed(s), Self::Owned(o)) => s.eq_ignore_ascii_case(o.as_str()),
#[cfg(feature = "alloc")]
(Self::Owned(s), Self::Borrowed(o)) => s.eq_ignore_ascii_case(o),
#[cfg(feature = "alloc")]
(Self::Owned(s), Self::Owned(o)) => s.eq_ignore_ascii_case(o.as_str()),
}
}
}
impl<'a> Hash for DnsNameInner<'a> {
fn hash<H: Hasher>(&self, state: &mut H) {
let s = match self {
Self::Borrowed(s) => s,
#[cfg(feature = "alloc")]
Self::Owned(s) => s.as_str(),
};
s.chars().for_each(|c| c.to_ascii_lowercase().hash(state));
}
}
impl<'a> fmt::Debug for DnsNameInner<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Borrowed(s) => f.write_fmt(format_args!("{:?}", s)),
#[cfg(feature = "alloc")]
Self::Owned(s) => f.write_fmt(format_args!("{:?}", s)),
}
}
}
#[derive(Debug)]
pub struct InvalidDnsNameError;
impl fmt::Display for InvalidDnsNameError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("invalid dns name")
}
}
#[cfg(feature = "std")]
impl StdError for InvalidDnsNameError {}
fn validate(input: &[u8]) -> Result<(), InvalidDnsNameError> {
enum State {
Start,
Next,
NumericOnly { len: usize },
NextAfterNumericOnly,
Subsequent { len: usize },
Hyphen { len: usize },
}
use State::*;
let mut state = Start;
const MAX_LABEL_LENGTH: usize = 63;
const MAX_NAME_LENGTH: usize = 253;
if input.len() > MAX_NAME_LENGTH {
return Err(InvalidDnsNameError);
}
for ch in input {
state = match (state, ch) {
(Start | Next | NextAfterNumericOnly | Hyphen { .. }, b'.') => {
return Err(InvalidDnsNameError)
}
(Subsequent { .. }, b'.') => Next,
(NumericOnly { .. }, b'.') => NextAfterNumericOnly,
(Subsequent { len } | NumericOnly { len } | Hyphen { len }, _)
if len >= MAX_LABEL_LENGTH =>
{
return Err(InvalidDnsNameError)
}
(Start | Next | NextAfterNumericOnly, b'0'..=b'9') => NumericOnly { len: 1 },
(NumericOnly { len }, b'0'..=b'9') => NumericOnly { len: len + 1 },
(Start | Next | NextAfterNumericOnly, b'a'..=b'z' | b'A'..=b'Z' | b'_') => {
Subsequent { len: 1 }
}
(Subsequent { len } | NumericOnly { len } | Hyphen { len }, b'-') => {
Hyphen { len: len + 1 }
}
(
Subsequent { len } | NumericOnly { len } | Hyphen { len },
b'a'..=b'z' | b'A'..=b'Z' | b'_' | b'0'..=b'9',
) => Subsequent { len: len + 1 },
_ => return Err(InvalidDnsNameError),
};
}
if matches!(
state,
Start | Hyphen { .. } | NumericOnly { .. } | NextAfterNumericOnly
) {
return Err(InvalidDnsNameError);
}
Ok(())
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum IpAddr {
V4(Ipv4Addr),
V6(Ipv6Addr),
}
impl TryFrom<&str> for IpAddr {
type Error = AddrParseError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match Ipv4Addr::try_from(value) {
Ok(v4) => Ok(Self::V4(v4)),
Err(_) => match Ipv6Addr::try_from(value) {
Ok(v6) => Ok(Self::V6(v6)),
Err(e) => Err(e),
},
}
}
}
#[cfg(feature = "std")]
impl From<std::net::IpAddr> for IpAddr {
fn from(addr: std::net::IpAddr) -> Self {
match addr {
std::net::IpAddr::V4(v4) => Self::V4(v4.into()),
std::net::IpAddr::V6(v6) => Self::V6(v6.into()),
}
}
}
#[cfg(feature = "std")]
impl From<IpAddr> for std::net::IpAddr {
fn from(value: IpAddr) -> Self {
match value {
IpAddr::V4(v4) => Self::from(std::net::Ipv4Addr::from(v4)),
IpAddr::V6(v6) => Self::from(std::net::Ipv6Addr::from(v6)),
}
}
}
#[cfg(feature = "std")]
impl From<std::net::Ipv4Addr> for IpAddr {
fn from(v4: std::net::Ipv4Addr) -> Self {
Self::V4(v4.into())
}
}
#[cfg(feature = "std")]
impl From<std::net::Ipv6Addr> for IpAddr {
fn from(v6: std::net::Ipv6Addr) -> Self {
Self::V6(v6.into())
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Ipv4Addr([u8; 4]);
impl TryFrom<&str> for Ipv4Addr {
type Error = AddrParseError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
if value.len() > 15 {
Err(AddrParseError(AddrKind::Ipv4))
} else {
Parser::new(value.as_bytes()).parse_with(|p| p.read_ipv4_addr(), AddrKind::Ipv4)
}
}
}
#[cfg(feature = "std")]
impl From<std::net::Ipv4Addr> for Ipv4Addr {
fn from(addr: std::net::Ipv4Addr) -> Self {
Self(addr.octets())
}
}
#[cfg(feature = "std")]
impl From<Ipv4Addr> for std::net::Ipv4Addr {
fn from(value: Ipv4Addr) -> Self {
Self::from(value.0)
}
}
impl AsRef<[u8; 4]> for Ipv4Addr {
fn as_ref(&self) -> &[u8; 4] {
&self.0
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Ipv6Addr([u8; 16]);
impl TryFrom<&str> for Ipv6Addr {
type Error = AddrParseError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Parser::new(value.as_bytes()).parse_with(|p| p.read_ipv6_addr(), AddrKind::Ipv6)
}
}
impl From<[u16; 8]> for Ipv6Addr {
fn from(value: [u16; 8]) -> Self {
let addr16 = [
value[0].to_be(),
value[1].to_be(),
value[2].to_be(),
value[3].to_be(),
value[4].to_be(),
value[5].to_be(),
value[6].to_be(),
value[7].to_be(),
];
Self(
unsafe { mem::transmute::<[u16; 8], [u8; 16]>(addr16) },
)
}
}
#[cfg(feature = "std")]
impl From<std::net::Ipv6Addr> for Ipv6Addr {
fn from(addr: std::net::Ipv6Addr) -> Self {
Self(addr.octets())
}
}
#[cfg(feature = "std")]
impl From<Ipv6Addr> for std::net::Ipv6Addr {
fn from(value: Ipv6Addr) -> Self {
Self::from(value.0)
}
}
impl AsRef<[u8; 16]> for Ipv6Addr {
fn as_ref(&self) -> &[u8; 16] {
&self.0
}
}
mod parser {
use super::{AddrParseError, Ipv4Addr, Ipv6Addr};
pub(super) struct Parser<'a> {
state: &'a [u8],
}
impl<'a> Parser<'a> {
pub(super) fn new(input: &'a [u8]) -> Self {
Parser { state: input }
}
fn read_atomically<T, F>(&mut self, inner: F) -> Option<T>
where
F: FnOnce(&mut Parser<'_>) -> Option<T>,
{
let state = self.state;
let result = inner(self);
if result.is_none() {
self.state = state;
}
result
}
pub(super) fn parse_with<T, F>(
&mut self,
inner: F,
kind: AddrKind,
) -> Result<T, AddrParseError>
where
F: FnOnce(&mut Parser<'_>) -> Option<T>,
{
let result = inner(self);
if self.state.is_empty() { result } else { None }.ok_or(AddrParseError(kind))
}
fn peek_char(&self) -> Option<char> {
self.state.first().map(|&b| char::from(b))
}
fn read_char(&mut self) -> Option<char> {
self.state.split_first().map(|(&b, tail)| {
self.state = tail;
char::from(b)
})
}
#[must_use]
fn read_given_char(&mut self, target: char) -> Option<()> {
self.read_atomically(|p| {
p.read_char()
.and_then(|c| if c == target { Some(()) } else { None })
})
}
fn read_separator<T, F>(&mut self, sep: char, index: usize, inner: F) -> Option<T>
where
F: FnOnce(&mut Parser<'_>) -> Option<T>,
{
self.read_atomically(move |p| {
if index > 0 {
p.read_given_char(sep)?;
}
inner(p)
})
}
fn read_number<T: ReadNumberHelper>(
&mut self,
radix: u32,
max_digits: Option<usize>,
allow_zero_prefix: bool,
) -> Option<T> {
self.read_atomically(move |p| {
let mut result = T::ZERO;
let mut digit_count = 0;
let has_leading_zero = p.peek_char() == Some('0');
while let Some(digit) = p.read_atomically(|p| p.read_char()?.to_digit(radix)) {
result = result.checked_mul(radix)?;
result = result.checked_add(digit)?;
digit_count += 1;
if let Some(max_digits) = max_digits {
if digit_count > max_digits {
return None;
}
}
}
if digit_count == 0 || (!allow_zero_prefix && has_leading_zero && digit_count > 1) {
None
} else {
Some(result)
}
})
}
pub(super) fn read_ipv4_addr(&mut self) -> Option<Ipv4Addr> {
self.read_atomically(|p| {
let mut groups = [0; 4];
for (i, slot) in groups.iter_mut().enumerate() {
*slot = p.read_separator('.', i, |p| {
p.read_number(10, Some(3), false)
})?;
}
Some(Ipv4Addr(groups))
})
}
pub(super) fn read_ipv6_addr(&mut self) -> Option<Ipv6Addr> {
fn read_groups(p: &mut Parser<'_>, groups: &mut [u16]) -> (usize, bool) {
let limit = groups.len();
for (i, slot) in groups.iter_mut().enumerate() {
if i < limit - 1 {
let ipv4 = p.read_separator(':', i, |p| p.read_ipv4_addr());
if let Some(v4_addr) = ipv4 {
let [one, two, three, four] = v4_addr.0;
groups[i] = u16::from_be_bytes([one, two]);
groups[i + 1] = u16::from_be_bytes([three, four]);
return (i + 2, true);
}
}
let group = p.read_separator(':', i, |p| p.read_number(16, Some(4), true));
match group {
Some(g) => *slot = g,
None => return (i, false),
}
}
(groups.len(), false)
}
self.read_atomically(|p| {
let mut head = [0; 8];
let (head_size, head_ipv4) = read_groups(p, &mut head);
if head_size == 8 {
return Some(head.into());
}
if head_ipv4 {
return None;
}
p.read_given_char(':')?;
p.read_given_char(':')?;
let mut tail = [0; 7];
let limit = 8 - (head_size + 1);
let (tail_size, _) = read_groups(p, &mut tail[..limit]);
head[(8 - tail_size)..8].copy_from_slice(&tail[..tail_size]);
Some(head.into())
})
}
}
trait ReadNumberHelper: Sized {
const ZERO: Self;
fn checked_mul(&self, other: u32) -> Option<Self>;
fn checked_add(&self, other: u32) -> Option<Self>;
}
macro_rules! impl_helper {
($($t:ty)*) => ($(impl ReadNumberHelper for $t {
const ZERO: Self = 0;
#[inline]
fn checked_mul(&self, other: u32) -> Option<Self> {
Self::checked_mul(*self, other.try_into().ok()?)
}
#[inline]
fn checked_add(&self, other: u32) -> Option<Self> {
Self::checked_add(*self, other.try_into().ok()?)
}
})*)
}
impl_helper! { u8 u16 u32 }
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub(super) enum AddrKind {
Ipv4,
Ipv6,
}
}
use parser::{AddrKind, Parser};
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct AddrParseError(AddrKind);
impl core::fmt::Display for AddrParseError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
f.write_str(match self.0 {
AddrKind::Ipv4 => "invalid IPv4 address syntax",
AddrKind::Ipv6 => "invalid IPv6 address syntax",
})
}
}
#[cfg(feature = "std")]
impl ::std::error::Error for AddrParseError {}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "alloc")]
use alloc::format;
#[cfg(feature = "alloc")]
static TESTS: &[(&str, bool)] = &[
("", false),
("localhost", true),
("LOCALHOST", true),
(".localhost", false),
("..localhost", false),
("1.2.3.4", false),
("127.0.0.1", false),
("absolute.", true),
("absolute..", false),
("multiple.labels.absolute.", true),
("foo.bar.com", true),
("infix-hyphen-allowed.com", true),
("-prefixhypheninvalid.com", false),
("suffixhypheninvalid--", false),
("suffixhypheninvalid-.com", false),
("foo.lastlabelendswithhyphen-", false),
("infix_underscore_allowed.com", true),
("_prefixunderscorevalid.com", true),
("labelendswithnumber1.bar.com", true),
("xn--bcher-kva.example", true),
(
"sixtythreesixtythreesixtythreesixtythreesixtythreesixtythreesix.com",
true,
),
(
"sixtyfoursixtyfoursixtyfoursixtyfoursixtyfoursixtyfoursixtyfours.com",
false,
),
(
"012345678901234567890123456789012345678901234567890123456789012.com",
true,
),
(
"0123456789012345678901234567890123456789012345678901234567890123.com",
false,
),
(
"01234567890123456789012345678901234567890123456789012345678901-.com",
false,
),
(
"012345678901234567890123456789012345678901234567890123456789012-.com",
false,
),
("numeric-only-final-label.1", false),
("numeric-only-final-label.absolute.1.", false),
("1starts-with-number.com", true),
("1Starts-with-number.com", true),
("1.2.3.4.com", true),
("123.numeric-only-first-label", true),
("a123b.com", true),
("numeric-only-middle-label.4.com", true),
("1000-sans.badssl.com", true),
("twohundredandfiftythreecharacters.twohundredandfiftythreecharacters.twohundredandfiftythreecharacters.twohundredandfiftythreecharacters.twohundredandfiftythreecharacters.twohundredandfiftythreecharacters.twohundredandfiftythreecharacters.twohundredandfi", true),
("twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourcharacters.twohundredandfiftyfourc", false),
];
#[cfg(feature = "alloc")]
#[test]
fn test_validation() {
for (input, expected) in TESTS {
#[cfg(feature = "std")]
println!("test: {:?} expected valid? {:?}", input, expected);
let name_ref = DnsName::try_from(*input);
assert_eq!(*expected, name_ref.is_ok());
let name = DnsName::try_from(input.to_string());
assert_eq!(*expected, name.is_ok());
}
}
#[cfg(feature = "alloc")]
#[test]
fn error_is_debug() {
assert_eq!(format!("{:?}", InvalidDnsNameError), "InvalidDnsNameError");
}
#[cfg(feature = "alloc")]
#[test]
fn error_is_display() {
assert_eq!(format!("{}", InvalidDnsNameError), "invalid dns name");
}
#[cfg(feature = "alloc")]
#[test]
fn dns_name_is_debug() {
let example = DnsName::try_from("example.com".to_string()).unwrap();
assert_eq!(format!("{:?}", example), "DnsName(\"example.com\")");
}
#[cfg(feature = "alloc")]
#[test]
fn dns_name_traits() {
let example = DnsName::try_from("example.com".to_string()).unwrap();
assert_eq!(example, example); #[cfg(feature = "std")]
{
use std::collections::HashSet;
let mut h = HashSet::<DnsName>::new();
h.insert(example);
}
}
#[cfg(feature = "alloc")]
#[test]
fn try_from_ascii_rejects_bad_utf8() {
assert_eq!(
format!("{:?}", DnsName::try_from(&b"\x80"[..])),
"Err(InvalidDnsNameError)"
);
}
const fn ipv4_address(
ip_address: &str,
octets: [u8; 4],
) -> (&str, Result<Ipv4Addr, AddrParseError>) {
(ip_address, Ok(Ipv4Addr(octets)))
}
const IPV4_ADDRESSES: &[(&str, Result<Ipv4Addr, AddrParseError>)] = &[
ipv4_address("0.0.0.0", [0, 0, 0, 0]),
ipv4_address("1.1.1.1", [1, 1, 1, 1]),
ipv4_address("205.0.0.0", [205, 0, 0, 0]),
ipv4_address("0.205.0.0", [0, 205, 0, 0]),
ipv4_address("0.0.205.0", [0, 0, 205, 0]),
ipv4_address("0.0.0.205", [0, 0, 0, 205]),
ipv4_address("0.0.0.20", [0, 0, 0, 20]),
("", Err(AddrParseError(AddrKind::Ipv4))),
("...", Err(AddrParseError(AddrKind::Ipv4))),
(".0.0.0.0", Err(AddrParseError(AddrKind::Ipv4))),
("0.0.0.0.", Err(AddrParseError(AddrKind::Ipv4))),
("0.0.0", Err(AddrParseError(AddrKind::Ipv4))),
("0.0.0.", Err(AddrParseError(AddrKind::Ipv4))),
("256.0.0.0", Err(AddrParseError(AddrKind::Ipv4))),
("0.256.0.0", Err(AddrParseError(AddrKind::Ipv4))),
("0.0.256.0", Err(AddrParseError(AddrKind::Ipv4))),
("0.0.0.256", Err(AddrParseError(AddrKind::Ipv4))),
("1..1.1.1", Err(AddrParseError(AddrKind::Ipv4))),
("1.1..1.1", Err(AddrParseError(AddrKind::Ipv4))),
("1.1.1..1", Err(AddrParseError(AddrKind::Ipv4))),
("025.0.0.0", Err(AddrParseError(AddrKind::Ipv4))),
("0.025.0.0", Err(AddrParseError(AddrKind::Ipv4))),
("0.0.025.0", Err(AddrParseError(AddrKind::Ipv4))),
("0.0.0.025", Err(AddrParseError(AddrKind::Ipv4))),
("1234.0.0.0", Err(AddrParseError(AddrKind::Ipv4))),
("0.1234.0.0", Err(AddrParseError(AddrKind::Ipv4))),
("0.0.1234.0", Err(AddrParseError(AddrKind::Ipv4))),
("0.0.0.1234", Err(AddrParseError(AddrKind::Ipv4))),
];
#[test]
fn parse_ipv4_address_test() {
for &(ip_address, expected_result) in IPV4_ADDRESSES {
assert_eq!(Ipv4Addr::try_from(ip_address), expected_result);
}
}
const fn ipv6_address(
ip_address: &str,
octets: [u8; 16],
) -> (&str, Result<Ipv6Addr, AddrParseError>) {
(ip_address, Ok(Ipv6Addr(octets)))
}
const IPV6_ADDRESSES: &[(&str, Result<Ipv6Addr, AddrParseError>)] = &[
ipv6_address(
"2a05:d018:076c:b685:e8ab:afd3:af51:3aed",
[
0x2a, 0x05, 0xd0, 0x18, 0x07, 0x6c, 0xb6, 0x85, 0xe8, 0xab, 0xaf, 0xd3, 0xaf, 0x51,
0x3a, 0xed,
],
),
ipv6_address(
"2A05:D018:076C:B685:E8AB:AFD3:AF51:3AED",
[
0x2a, 0x05, 0xd0, 0x18, 0x07, 0x6c, 0xb6, 0x85, 0xe8, 0xab, 0xaf, 0xd3, 0xaf, 0x51,
0x3a, 0xed,
],
),
ipv6_address(
"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
[
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff,
],
),
ipv6_address(
"FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF",
[
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff,
],
),
ipv6_address(
"FFFF:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
[
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff,
],
),
(
"ffgf:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
Err(AddrParseError(AddrKind::Ipv6)),
),
(
"ffff:gfff:ffff:ffff:ffff:ffff:ffff:ffff",
Err(AddrParseError(AddrKind::Ipv6)),
),
(
"ffff:ffff:fffg:ffff:ffff:ffff:ffff:ffff",
Err(AddrParseError(AddrKind::Ipv6)),
),
(
"ffff:ffff:ffff:ffgf:ffff:ffff:ffff:ffff",
Err(AddrParseError(AddrKind::Ipv6)),
),
(
"ffff:ffff:ffff:ffff:gfff:ffff:ffff:ffff",
Err(AddrParseError(AddrKind::Ipv6)),
),
(
"ffff:ffff:ffff:ffff:ffff:fgff:ffff:ffff",
Err(AddrParseError(AddrKind::Ipv6)),
),
(
"ffff:ffff:ffff:ffff:ffff:ffff:ffgf:ffff",
Err(AddrParseError(AddrKind::Ipv6)),
),
(
"ffff:ffff:ffff:ffff:ffff:ffff:ffgf:fffg",
Err(AddrParseError(AddrKind::Ipv6)),
),
(
":ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
Err(AddrParseError(AddrKind::Ipv6)),
),
(
"ffff::ffff:ffff:ffff:ffff:ffff:ffff:ffff",
Err(AddrParseError(AddrKind::Ipv6)),
),
(
"ffff:ffff::ffff:ffff:ffff:ffff:ffff:ffff",
Err(AddrParseError(AddrKind::Ipv6)),
),
(
"ffff:ffff:ffff::ffff:ffff:ffff:ffff:ffff",
Err(AddrParseError(AddrKind::Ipv6)),
),
(
"ffff:ffff:ffff:ffff::ffff:ffff:ffff:ffff",
Err(AddrParseError(AddrKind::Ipv6)),
),
(
"ffff:ffff:ffff:ffff:ffff::ffff:ffff:ffff",
Err(AddrParseError(AddrKind::Ipv6)),
),
(
"ffff:ffff:ffff:ffff:ffff:ffff::ffff:ffff",
Err(AddrParseError(AddrKind::Ipv6)),
),
(
"ffff:ffff:ffff:ffff:ffff:ffff:ffff::ffff",
Err(AddrParseError(AddrKind::Ipv6)),
),
(
"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:",
Err(AddrParseError(AddrKind::Ipv6)),
),
(
"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
Err(AddrParseError(AddrKind::Ipv6)),
),
(
"ga05:d018:076c:b685:e8ab:afd3:af51:3aed",
Err(AddrParseError(AddrKind::Ipv6)),
),
(
":a05:d018:076c:b685:e8ab:afd3:af51:3aed",
Err(AddrParseError(AddrKind::Ipv6)),
),
(
"2a05:d018:076c:b685:e8ab:afd3:af51:3ae:",
Err(AddrParseError(AddrKind::Ipv6)),
),
(
"2a05:d018:076c:b685:e8ab:afd3:af51:3a::",
Err(AddrParseError(AddrKind::Ipv6)),
),
(
"2a05::018:076c:b685:e8ab:afd3:af51:3aed",
Err(AddrParseError(AddrKind::Ipv6)),
),
(
"2a056:d018:076c:b685:e8ab:afd3:af51:3ae",
Err(AddrParseError(AddrKind::Ipv6)),
),
(
"2a0:d018:076c:b685:e8ab:afd3:af51:3aed ",
Err(AddrParseError(AddrKind::Ipv6)),
),
(
"d018:076c:b685:e8ab:afd3:af51:3aed",
Err(AddrParseError(AddrKind::Ipv6)),
),
(
"2a05:d018:076c:b685:e8ab:afd3:af51:3aed3aed",
Err(AddrParseError(AddrKind::Ipv6)),
),
];
#[test]
fn parse_ipv6_address_test() {
for &(ip_address, expected_result) in IPV6_ADDRESSES {
assert_eq!(Ipv6Addr::try_from(ip_address), expected_result);
}
}
#[test]
fn try_from_ascii_ip_address_test() {
const IP_ADDRESSES: &[(&str, Result<IpAddr, AddrParseError>)] = &[
("127.0.0.1", Ok(IpAddr::V4(Ipv4Addr([127, 0, 0, 1])))),
(
"127.0.0.",
Err(AddrParseError(AddrKind::Ipv6)),
),
(
"0000:0000:0000:0000:0000:0000:0000:0001",
Ok(IpAddr::V6(Ipv6Addr([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
]))),
),
(
"example.com",
Err(AddrParseError(AddrKind::Ipv6)),
),
];
for &(ip_address, expected_result) in IP_ADDRESSES {
assert_eq!(IpAddr::try_from(ip_address), expected_result)
}
}
#[test]
fn try_from_ascii_str_ip_address_test() {
const IP_ADDRESSES: &[(&str, Result<IpAddr, AddrParseError>)] = &[
("127.0.0.1", Ok(IpAddr::V4(Ipv4Addr([127, 0, 0, 1])))),
(
"127.0.0.",
Err(AddrParseError(AddrKind::Ipv6)),
),
(
"0000:0000:0000:0000:0000:0000:0000:0001",
Ok(IpAddr::V6(Ipv6Addr([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
]))),
),
(
"example.com",
Err(AddrParseError(AddrKind::Ipv6)),
),
];
for &(ip_address, expected_result) in IP_ADDRESSES {
assert_eq!(IpAddr::try_from(ip_address), expected_result)
}
}
#[test]
#[cfg(feature = "std")]
fn to_str() {
let domain_str = "example.com";
let domain_servername = ServerName::try_from(domain_str).unwrap();
assert_eq!(domain_str, domain_servername.to_str());
let ipv4_str = "127.0.0.1";
let ipv4_servername = ServerName::try_from("127.0.0.1").unwrap();
assert_eq!(ipv4_str, ipv4_servername.to_str());
let ipv6_str = "::1";
let ipv6_servername = ServerName::try_from(ipv6_str).unwrap();
assert_eq!("::1", ipv6_servername.to_str());
}
}