use core::ops;
use std::ffi::{CStr, CString};
use std::fmt;
use std::fmt::Formatter;
use crate::check::*;
use crate::*;
use std::borrow::Borrow;
use std::hash::Hash;
use std::str::FromStr;
#[derive(Debug, Clone, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct FQDN(pub(crate) CString);
impl FQDN {
pub fn from_vec(mut bytes: Vec<u8>) -> Result<Self, Error> {
if bytes.last() != Some(&0) {
bytes.push(0);
}
#[cfg(feature = "domain-name-length-limited-to-255")]
if bytes.len() > 254 {
return Err(Error::TooLongDomainName);
}
let tochecklen = bytes.len() - 1;
let mut tocheck = &mut bytes[..tochecklen];
while !tocheck.is_empty() {
match tocheck[0] as usize {
l if l >= tocheck.len() => {
return Err(Error::InvalidStructure);
}
#[cfg(feature = "domain-label-length-limited-to-63")]
l if l > 63 => {
return Err(Error::TooLongLabel);
}
0 => {
return Err(Error::EmptyLabel);
}
l => {
tocheck
.iter_mut()
.skip(1) .take(l) .try_for_each(|c| {
*c = check_and_lower_any_char(*c)?;
Ok::<(), Error>(())
})?;
tocheck = &mut tocheck[l + 1..];
}
}
}
Ok(unsafe { Self::from_vec_with_nul_unchecked(bytes) })
}
unsafe fn from_vec_with_nul_unchecked(v: Vec<u8>) -> Self {
FQDN(CString::from_vec_with_nul_unchecked(v))
}
pub fn from_ascii_str(s: &str) -> Result<Self, Error> {
let s = s.as_bytes();
let toparse = match s.last() {
None => {
#[cfg(feature = "domain-name-should-have-trailing-dot")]
return Err(Error::TrailingDotMissing);
#[cfg(not(feature = "domain-name-should-have-trailing-dot"))]
return Ok(Self(CString::default()));
}
Some(&b'.') => {
if s.len() == 1 {
return Ok(Self(CString::default()));
}
&s[..s.len() - 1]
}
_ => {
#[cfg(feature = "domain-name-should-have-trailing-dot")]
return Err(Error::TrailingDotMissing);
#[cfg(not(feature = "domain-name-should-have-trailing-dot"))]
s }
};
#[cfg(feature = "domain-name-length-limited-to-255")]
if toparse.len() > 253 {
return Err(Error::TooLongDomainName);
}
toparse
.split(|&c| c == b'.')
.try_fold(
Vec::with_capacity(s.len() + 1),
|mut bytes, label| match label.len() {
#[cfg(feature = "domain-label-length-limited-to-63")]
l if l > 63 => Err(Error::TooLongLabel),
#[cfg(not(feature = "domain-label-length-limited-to-63"))]
l if l > 255 => Err(Error::TooLongLabel),
0 => Err(Error::EmptyLabel),
l => {
let mut iter = label.iter();
bytes.push(l as u8);
#[cfg(feature = "domain-label-cannot-start-or-end-with-hyphen")]
{
let first = check_and_lower_any_char(*iter.next().unwrap())?;
if first == '-' as u8 {
return Err(Error::LabelCannotStartWithHyphen);
} else {
bytes.push(first);
}
}
iter.try_for_each(|&c| {
bytes.push(check_and_lower_any_char(c)?);
Ok(())
})?;
#[cfg(feature = "domain-label-cannot-start-or-end-with-hyphen")]
{
if *bytes.last().unwrap() == '-' as u8 {
return Err(Error::LabelCannotEndWithHyphen);
}
}
Ok(bytes)
}
},
)
.map(|bytes| Self(unsafe { CString::from_vec_unchecked(bytes) }))
}
}
impl AsRef<Fqdn> for FQDN {
#[inline]
fn as_ref(&self) -> &Fqdn {
unsafe { &*(self.0.as_c_str() as *const CStr as *const Fqdn) }
}
}
impl ops::Deref for FQDN {
type Target = Fqdn;
#[inline]
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
impl From<&Fqdn> for FQDN {
#[inline]
fn from(s: &Fqdn) -> FQDN {
FQDN(s.0.into())
}
}
impl From<Box<Fqdn>> for FQDN {
#[inline]
fn from(s: Box<Fqdn>) -> FQDN {
let cstr: Box<CStr> = unsafe { std::mem::transmute(s) };
FQDN(cstr.into())
}
}
impl TryFrom<Vec<u8>> for FQDN {
type Error = Error;
#[inline]
fn try_from(bytes: Vec<u8>) -> Result<FQDN, Self::Error> {
Self::from_vec(bytes)
}
}
impl Borrow<Fqdn> for FQDN {
#[inline]
fn borrow(&self) -> &Fqdn {
self.as_ref()
}
}
impl fmt::Display for FQDN {
#[inline]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
self.as_ref().fmt(f)
}
}
impl FromStr for FQDN {
type Err = Error;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[cfg(feature = "punycode")]
{
Self::punyencode(s)
}
#[cfg(not(feature = "punycode"))]
{
Self::from_ascii_str(s)
}
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for FQDN {
#[inline]
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.to_string().serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for FQDN {
#[inline]
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
String::deserialize(deserializer)
.and_then(|str| Self::from_str(&str).map_err(serde::de::Error::custom))
}
}