#[allow(clippy::useless_attribute)]
#[allow(unused)]
#[allow(deprecated)]
use std::ascii::AsciiExt;
use std::borrow::Borrow;
use std::cmp::{Ordering, PartialEq};
use std::fmt::{self, Debug, Display, Formatter, Write};
use std::hash::{Hash, Hasher};
use tinyvec::TinyVec;
use idna;
use tracing::debug;
use crate::error::*;
const WILDCARD: &[u8] = b"*";
const IDNA_PREFIX: &[u8] = b"xn--";
#[derive(Clone, Eq)]
pub struct Label(TinyVec<[u8; 24]>);
impl Label {
pub fn from_raw_bytes(bytes: &[u8]) -> ProtoResult<Self> {
if bytes.is_empty() {
return Err("Label requires a minimum length of 1".into());
}
if bytes.len() > 63 {
return Err(ProtoErrorKind::LabelBytesTooLong(bytes.len()).into());
};
Ok(Self(TinyVec::from(bytes)))
}
pub fn from_utf8(s: &str) -> ProtoResult<Self> {
if s.as_bytes() == WILDCARD {
return Ok(Self::wildcard());
}
if s.starts_with('_') {
return Self::from_ascii(s);
}
match idna::Config::default()
.use_std3_ascii_rules(true)
.transitional_processing(true)
.verify_dns_length(false)
.to_ascii(s)
{
Ok(puny) => Self::from_ascii(&puny),
e => Err(format!("Label contains invalid characters: {e:?}").into()),
}
}
pub fn from_ascii(s: &str) -> ProtoResult<Self> {
if s.len() > 63 {
return Err(ProtoErrorKind::LabelBytesTooLong(s.len()).into());
}
if s.as_bytes() == WILDCARD {
return Ok(Self::wildcard());
}
if !s.is_empty()
&& s.is_ascii()
&& s.chars().take(1).all(|c| is_safe_ascii(c, true, false))
&& s.chars().skip(1).all(|c| is_safe_ascii(c, false, false))
{
Self::from_raw_bytes(s.as_bytes())
} else {
Err(format!("Malformed label: {s}").into())
}
}
pub fn wildcard() -> Self {
Self(TinyVec::from(WILDCARD))
}
pub fn to_lowercase(&self) -> Self {
if let Some((idx, _)) = self
.0
.iter()
.enumerate()
.find(|&(_, c)| *c != c.to_ascii_lowercase())
{
let mut lower_label: Vec<u8> = self.0.to_vec();
lower_label[idx..].make_ascii_lowercase();
Self(TinyVec::from(lower_label.as_slice()))
} else {
self.clone()
}
}
pub fn is_wildcard(&self) -> bool {
self.as_bytes() == WILDCARD
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
pub fn eq_ignore_ascii_case(&self, other: &Self) -> bool {
self.0.eq_ignore_ascii_case(&other.0)
}
pub fn cmp_with_f<F: LabelCmp>(&self, other: &Self) -> Ordering {
let s = self.0.iter();
let o = other.0.iter();
for (s, o) in s.zip(o) {
match F::cmp_u8(*s, *o) {
Ordering::Equal => continue,
not_eq => return not_eq,
}
}
self.0.len().cmp(&other.0.len())
}
pub fn to_utf8(&self) -> String {
format!("{self}")
}
pub fn to_ascii(&self) -> String {
let mut ascii = String::with_capacity(self.as_bytes().len());
self.write_ascii(&mut ascii)
.expect("should never fail to write a new string");
ascii
}
pub fn write_ascii<W: Write>(&self, f: &mut W) -> Result<(), fmt::Error> {
fn escape_non_ascii<W: Write>(
byte: u8,
f: &mut W,
is_first: bool,
) -> Result<(), fmt::Error> {
let to_triple_escape = |ch: u8| format!("\\{ch:03o}");
let to_single_escape = |ch: char| format!("\\{ch}");
match char::from(byte) {
c if is_safe_ascii(c, is_first, true) => f.write_char(c)?,
c if byte > b'\x20' && byte < b'\x7f' => f.write_str(&to_single_escape(c))?,
_ => f.write_str(&to_triple_escape(byte))?,
}
Ok(())
}
let mut chars = self.as_bytes().iter();
if let Some(ch) = chars.next() {
escape_non_ascii(*ch, f, true)?;
}
for ch in chars {
escape_non_ascii(*ch, f, false)?;
}
Ok(())
}
}
impl AsRef<[u8]> for Label {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl Borrow<[u8]> for Label {
fn borrow(&self) -> &[u8] {
&self.0
}
}
fn is_safe_ascii(c: char, is_first: bool, for_encoding: bool) -> bool {
match c {
c if !c.is_ascii() => false,
c if c.is_alphanumeric() => true,
'-' if !is_first => true, '_' => true, '*' if is_first => true, '.' if !for_encoding => true, _ => false,
}
}
impl Display for Label {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
if self.as_bytes().starts_with(IDNA_PREFIX) {
let label = String::from_utf8_lossy(self.borrow());
let (label, e) = idna::Config::default()
.use_std3_ascii_rules(false)
.transitional_processing(false)
.verify_dns_length(false)
.to_unicode(&label);
if e.is_ok() {
return f.write_str(&label);
} else {
debug!(
"xn-- prefixed string did not translate via IDNA properly: {:?}",
e
)
}
}
self.write_ascii(f)
}
}
impl Debug for Label {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
let label = String::from_utf8_lossy(self.borrow());
f.write_str(&label)
}
}
impl PartialEq<Self> for Label {
fn eq(&self, other: &Self) -> bool {
self.eq_ignore_ascii_case(other)
}
}
impl PartialOrd<Self> for Label {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Label {
fn cmp(&self, other: &Self) -> Ordering {
self.cmp_with_f::<CaseInsensitive>(other)
}
}
impl Hash for Label {
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
{
for b in self.borrow() as &[u8] {
state.write_u8(b.to_ascii_lowercase());
}
}
}
pub trait LabelCmp {
fn cmp_u8(l: u8, r: u8) -> Ordering;
}
pub(super) struct CaseSensitive;
impl LabelCmp for CaseSensitive {
fn cmp_u8(l: u8, r: u8) -> Ordering {
l.cmp(&r)
}
}
pub(super) struct CaseInsensitive;
impl LabelCmp for CaseInsensitive {
fn cmp_u8(l: u8, r: u8) -> Ordering {
l.to_ascii_lowercase().cmp(&r.to_ascii_lowercase())
}
}
pub trait IntoLabel: Sized {
fn into_label(self) -> ProtoResult<Label>;
}
impl<'a> IntoLabel for &'a Label {
fn into_label(self) -> ProtoResult<Label> {
Ok(self.clone())
}
}
impl IntoLabel for Label {
fn into_label(self) -> ProtoResult<Label> {
Ok(self)
}
}
impl<'a> IntoLabel for &'a str {
fn into_label(self) -> ProtoResult<Label> {
Label::from_utf8(self)
}
}
impl IntoLabel for String {
fn into_label(self) -> ProtoResult<Label> {
Label::from_utf8(&self)
}
}
impl<'a> IntoLabel for &'a [u8] {
fn into_label(self) -> ProtoResult<Label> {
Label::from_raw_bytes(self)
}
}
impl IntoLabel for Vec<u8> {
fn into_label(self) -> ProtoResult<Label> {
Label::from_raw_bytes(&self)
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::dbg_macro, clippy::print_stdout)]
use super::*;
#[test]
fn test_encoding() {
assert_eq!(
Label::from_utf8("abc").unwrap(),
Label::from_raw_bytes(b"abc").unwrap()
);
assert_eq!(
Label::from_utf8("ABC").unwrap(),
Label::from_raw_bytes(b"ABC").unwrap()
);
assert_eq!(
Label::from_utf8("🦀").unwrap(),
Label::from_raw_bytes(b"xn--zs9h").unwrap()
);
assert_eq!(
Label::from_utf8("rust-🦀-icon").unwrap(),
Label::from_raw_bytes(b"xn--rust--icon-9447i").unwrap()
);
assert_eq!(
Label::from_ascii("ben.fry").unwrap(),
Label::from_raw_bytes(b"ben.fry").unwrap()
);
assert_eq!(Label::from_utf8("🦀").unwrap().to_utf8(), "🦀");
assert_eq!(Label::from_utf8("🦀").unwrap().to_ascii(), "xn--zs9h");
}
fn assert_panic_label_too_long(error: ProtoResult<Label>, len: usize) {
eprintln!("{error:?}");
assert!(error.is_err());
match error.unwrap_err().kind() {
ProtoErrorKind::LabelBytesTooLong(n) if *n == len => (),
ProtoErrorKind::LabelBytesTooLong(e) => {
panic!(
"LabelTooLongError error don't report expected size {} of the label provided.",
e
)
}
_ => panic!("Should have returned a LabelTooLongError"),
}
}
#[test]
fn test_label_too_long_ascii_with_utf8() {
let label_too_long = "alwaystestingcodewithatoolonglabeltoolongtofitin63bytesisagoodhabit";
let error = Label::from_utf8(label_too_long);
assert_panic_label_too_long(error, label_too_long.len());
}
#[test]
fn test_label_too_long_utf8_puny_emoji() {
let emoji_case = "💜🦀🏖️🖥️😨🚀✨🤖💚🦾🦿😱😨✉️👺📚💻🗓️🤡🦀😈🚀💀⚡🦄";
let error = Label::from_utf8(emoji_case);
assert_panic_label_too_long(error, 64);
}
#[test]
fn test_label_too_long_utf8_puny_emoji_mixed() {
let emoji_case = "こんにちは-I-mögen-jesień-café-🦀-intéressant";
let error = Label::from_utf8(emoji_case);
assert_panic_label_too_long(error, 65);
}
#[test]
fn test_label_too_long_utf8_puny_mixed() {
let edge_case = "🦀testwithalonglabelinutf8tofitin63octetsisagoodhabit🦀";
let error = Label::from_utf8(edge_case);
assert_panic_label_too_long(error, 64);
}
#[test]
fn test_label_too_long_raw() {
let label_too_long = b"alwaystestingcodewithatoolonglabeltoolongtofitin63bytesisagoodhabit";
let error = Label::from_raw_bytes(label_too_long);
assert_panic_label_too_long(error, label_too_long.len());
}
#[test]
fn test_label_too_long_ascii() {
let label_too_long = "alwaystestingcodewithatoolonglabeltoolongtofitin63bytesisagoodhabit";
let error = Label::from_ascii(label_too_long);
assert_panic_label_too_long(error, label_too_long.len());
}
#[test]
fn test_decoding() {
assert_eq!(Label::from_raw_bytes(b"abc").unwrap().to_string(), "abc");
assert_eq!(
Label::from_raw_bytes(b"xn--zs9h").unwrap().to_string(),
"🦀"
);
assert_eq!(
Label::from_raw_bytes(b"xn--rust--icon-9447i")
.unwrap()
.to_string(),
"rust-🦀-icon"
);
}
#[test]
fn test_from_ascii_adversial_utf8() {
let expect_err = Label::from_ascii("🦀");
assert!(expect_err.is_err());
}
#[test]
fn test_to_lowercase() {
assert_ne!(Label::from_ascii("ABC").unwrap().to_string(), "abc");
assert_ne!(Label::from_ascii("abcDEF").unwrap().to_string(), "abcdef");
assert_eq!(
Label::from_ascii("ABC").unwrap().to_lowercase().to_string(),
"abc"
);
assert_eq!(
Label::from_ascii("abcDEF")
.unwrap()
.to_lowercase()
.to_string(),
"abcdef"
);
}
#[test]
fn test_to_cmp_f() {
assert_eq!(
Label::from_ascii("ABC")
.unwrap()
.cmp_with_f::<CaseInsensitive>(&Label::from_ascii("abc").unwrap()),
Ordering::Equal
);
assert_eq!(
Label::from_ascii("abcDEF")
.unwrap()
.cmp_with_f::<CaseInsensitive>(&Label::from_ascii("abcdef").unwrap()),
Ordering::Equal
);
assert_eq!(
Label::from_ascii("ABC")
.unwrap()
.cmp_with_f::<CaseSensitive>(&Label::from_ascii("abc").unwrap()),
Ordering::Less
);
assert_eq!(
Label::from_ascii("abcDEF")
.unwrap()
.cmp_with_f::<CaseSensitive>(&Label::from_ascii("abcdef").unwrap()),
Ordering::Less
);
}
#[test]
fn test_partial_cmp() {
let comparisons: Vec<(Label, Label)> = vec![
(
Label::from_raw_bytes(b"yljkjljk").unwrap(),
Label::from_raw_bytes(b"Z").unwrap(),
),
(
Label::from_raw_bytes(b"Z").unwrap(),
Label::from_raw_bytes(b"zABC").unwrap(),
),
(
Label::from_raw_bytes(&[1]).unwrap(),
Label::from_raw_bytes(b"*").unwrap(),
),
(
Label::from_raw_bytes(b"*").unwrap(),
Label::from_raw_bytes(&[200]).unwrap(),
),
];
for (left, right) in comparisons {
println!("left: {left}, right: {right}");
assert_eq!(left.cmp(&right), Ordering::Less);
}
}
#[test]
fn test_is_wildcard() {
assert!(Label::from_raw_bytes(b"*").unwrap().is_wildcard());
assert!(Label::from_ascii("*").unwrap().is_wildcard());
assert!(Label::from_utf8("*").unwrap().is_wildcard());
assert!(!Label::from_raw_bytes(b"abc").unwrap().is_wildcard());
}
#[test]
fn test_ascii_escape() {
assert_eq!(
Label::from_raw_bytes(&[0o200]).unwrap().to_string(),
"\\200"
);
assert_eq!(
Label::from_raw_bytes(&[0o001]).unwrap().to_string(),
"\\001"
);
assert_eq!(Label::from_ascii(".").unwrap().to_ascii(), "\\.");
assert_eq!(
Label::from_ascii("ben.fry").unwrap().to_string(),
"ben\\.fry"
);
assert_eq!(Label::from_raw_bytes(&[0o200]).unwrap().to_ascii(), "\\200");
}
}