ascii_domain

Domains whose labels are only ASCII.
git clone https://git.philomathiclife.com/repos/ascii_domain
Log | Files | Refs | README

char_set.rs (23046B)


      1 use core::{
      2     error::Error,
      3     fmt::{self, Display, Formatter},
      4     str,
      5 };
      6 /// Error returned from [`AllowedAscii::try_from_unique_ascii`].
      7 #[expect(variant_size_differences, reason = "usize is fine in size")]
      8 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
      9 pub enum AsciiErr {
     10     /// Since `AllowedAscii` only allows unique ASCII characters and doesn't allow `b'.'`, the maximum count is
     11     /// 127. This variant is returned when the count exceeds that.
     12     CountTooLarge(usize),
     13     /// The contained `u8` is not valid ASCII (i.e., it is strictly greater than 127).
     14     InvalidByte(u8),
     15     /// `b'.'` was in the allowed ASCII. It is the only ASCII value not allowed since it is always used
     16     /// as a [`crate::dom::Label`] separator.
     17     Contains46,
     18     /// The contained ASCII appeared more than once.
     19     Duplicate(u8),
     20 }
     21 impl Display for AsciiErr {
     22     #[inline]
     23     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
     24         match *self {
     25             Self::CountTooLarge(byt) => {
     26                 write!(f, "the allowed ASCII had {byt} values, but 127 is the max")
     27             }
     28             Self::InvalidByte(byt) => {
     29                 write!(f, "allowed ASCII was passed the invalid byte value {byt}")
     30             }
     31             Self::Contains46 => f.write_str("allowed ASCII contains '.'"),
     32             Self::Duplicate(byt) => {
     33                 let input = [byt];
     34                 if let Ok(val) = str::from_utf8(input.as_slice()) {
     35                     write!(f, "allowed ASCII has the duplicate value '{val}'")
     36                 } else {
     37                     write!(f, "allowed ASCII has the invalid value '{byt}'")
     38                 }
     39             }
     40         }
     41     }
     42 }
     43 impl Error for AsciiErr {}
     44 /// Container of the ASCII `u8`s that are allowed to appear in a [`crate::dom::Label`].
     45 ///
     46 /// Note that while
     47 /// [`crate::dom::Domain`] treats ASCII uppercase letters as lowercase, it still depends on such `u8`s being
     48 /// included. For example if `b'A'` is not included, then `b'A'` is not allowed even if `b'a'` is included.
     49 ///
     50 /// It is _highly_ unlikely that non-printable ASCII nor `b'\\'` should be used since such ASCII would almost
     51 /// certainly require being escaped.
     52 #[derive(Debug)]
     53 pub struct AllowedAscii<T> {
     54     /// The allowed ASCII `u8`s.
     55     allowed: T,
     56 }
     57 impl<T> AllowedAscii<T> {
     58     /// Returns a reference to the contained value.
     59     ///
     60     /// # Example
     61     ///
     62     /// ```
     63     /// use ascii_domain::char_set;
     64     /// assert!(char_set::ASCII_LETTERS.as_inner().len() == 52);
     65     /// ```
     66     #[inline]
     67     pub const fn as_inner(&self) -> &T {
     68         &self.allowed
     69     }
     70     /// Returns the contained value consuming `self`.
     71     ///
     72     /// # Example
     73     ///
     74     /// ```
     75     /// use ascii_domain::char_set;
     76     /// assert!(char_set::ASCII_LETTERS.into_inner().len() == 52);
     77     /// ```
     78     #[inline]
     79     pub fn into_inner(self) -> T {
     80         self.allowed
     81     }
     82 }
     83 impl<T: AsRef<[u8]>> AllowedAscii<T> {
     84     /// Returns `true` iff `val` is an allowed ASCII value in a [`crate::dom::Label`].
     85     ///
     86     /// # Example
     87     ///
     88     /// ```
     89     /// use ascii_domain::char_set;
     90     /// assert!(char_set::ASCII_LETTERS.contains(b'a'));
     91     /// ```
     92     #[inline]
     93     #[must_use]
     94     pub fn contains(&self, val: u8) -> bool {
     95         // We sort `allowed` in `try_from_unique_ascii`, so `binary_search` is fine.
     96         self.allowed.as_ref().binary_search(&val).is_ok()
     97     }
     98     /// Returns the number of allowed ASCII characters.
     99     ///
    100     /// # Example
    101     ///
    102     /// ```
    103     /// use ascii_domain::char_set;
    104     /// assert!(char_set::ASCII_LETTERS.len() == 52);
    105     /// ```
    106     #[expect(
    107         clippy::as_conversions,
    108         clippy::cast_possible_truncation,
    109         reason = "comment justifies its correctness"
    110     )]
    111     #[inline]
    112     #[must_use]
    113     pub fn len(&self) -> u8 {
    114         // We enforce only unique non `b'.'` ASCII in `try_from_unique_ascii` which among other things means
    115         // the max count is 127 so truncation will not occur
    116         self.allowed.as_ref().len() as u8
    117     }
    118     /// Returns `true` iff `self` does not contain any ASCII.
    119     ///
    120     /// # Examples
    121     ///
    122     /// ```
    123     /// use ascii_domain::char_set::{self, AllowedAscii};
    124     /// assert!(!char_set::ASCII_LETTERS.is_empty());
    125     /// assert!(AllowedAscii::try_from_unique_ascii([]).unwrap().is_empty());
    126     /// ```
    127     #[inline]
    128     #[must_use]
    129     pub fn is_empty(&self) -> bool {
    130         self.allowed.as_ref().is_empty()
    131     }
    132 }
    133 impl<T: AsMut<[u8]>> AllowedAscii<T> {
    134     /// `allowed` must contain unique ASCII `u8`s. Note it is likely `allowed` should be a subset of
    135     /// [`PRINTABLE_ASCII`] since any other ASCII would likely require some form of escape character logic.
    136     /// Additionally, it is likely `allowed.as_mut().len()` should be greater than 0; otherwise the returned
    137     /// `AllowedAscii` will always cause [`crate::dom::Domain::try_from_bytes`] to error since `Domain` requires
    138     /// at least one non-root [`crate::dom::Label`].
    139     ///
    140     /// `allowed` is mutated such that `allowed.as_mut()` is sorted in order.
    141     ///
    142     /// # Errors
    143     ///
    144     /// Returns `AsciiError` iff `allowed` does not contain a set of unique ASCII `u8`s or contains `b'.'`.
    145     ///
    146     /// # Examples
    147     ///
    148     /// ```
    149     /// use ascii_domain::char_set::{AllowedAscii, AsciiErr};
    150     /// assert!(AllowedAscii::try_from_unique_ascii(b"asdfghjkl".to_owned()).map_or(false, |ascii| ascii.contains(b'a') && !ascii.contains(b'A')));
    151     /// assert!(AllowedAscii::try_from_unique_ascii(b"aa".to_owned()).map_or_else(|err| err == AsciiErr::Duplicate(b'a'), |_| false));
    152     /// assert!(AllowedAscii::try_from_unique_ascii([255]).map_or_else(|err| err == AsciiErr::InvalidByte(255), |_| false));
    153     /// assert!(AllowedAscii::try_from_unique_ascii([0; 128]).map_or_else(|err| err == AsciiErr::CountTooLarge(128), |_| false));
    154     /// assert!(AllowedAscii::try_from_unique_ascii([b'.']).map_or_else(|err| err == AsciiErr::Contains46, |_| false));
    155     /// ```
    156     #[inline]
    157     pub fn try_from_unique_ascii(mut allowed: T) -> Result<Self, AsciiErr> {
    158         let bytes = allowed.as_mut();
    159         if bytes.len() > 127 {
    160             Err(AsciiErr::CountTooLarge(bytes.len()))
    161         } else {
    162             bytes.sort_unstable();
    163             // Since `bytes` is sorted, we simply have to check the last value to determine if valid ASCII was
    164             // provided.
    165             if let Some(byt) = bytes.last() {
    166                 let b = *byt;
    167                 if b > 127 {
    168                     return Err(AsciiErr::InvalidByte(b));
    169                 }
    170             }
    171             bytes
    172                 .iter()
    173                 // 255 is not valid ASCII, so we can use it as an initializer.
    174                 .try_fold(255, |prev, b| {
    175                     let byt = *b;
    176                     if byt == b'.' {
    177                         Err(AsciiErr::Contains46)
    178                     } else if prev == byt {
    179                         Err(AsciiErr::Duplicate(prev))
    180                     } else {
    181                         Ok(byt)
    182                     }
    183                 })
    184                 .map(|_| Self { allowed })
    185         }
    186     }
    187 }
    188 /// Printable ASCII that should not need to be "escaped".
    189 ///
    190 /// That is to say printable ASCII excluding space (i.e., 32), dot (i.e. 46), and backslash (i.e., 92).
    191 /// This returns all `u8`s inclusively between 33 and 126 except 46 and 92.
    192 pub const PRINTABLE_ASCII: AllowedAscii<[u8; 92]> = AllowedAscii {
    193     allowed: [
    194         b'!', b'"', b'#', b'$', b'%', b'&', b'\'', b'(', b')', b'*', b'+', b',', b'-', b'/', b'0',
    195         b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b':', b';', b'<', b'=', b'>', b'?',
    196         b'@', b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N',
    197         b'O', b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', b'[', b']', b'^',
    198         b'_', b'`', b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm',
    199         b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'{', b'|',
    200         b'}', b'~',
    201     ],
    202 };
    203 /// ASCII allowed in [RFC 5322 `atext`](https://www.rfc-editor.org/rfc/rfc5322#section-3.2.3).
    204 /// This contains the following `u8`s:
    205 ///
    206 /// 33, 35–39, 42–43, 45, 47–57, 61, 63, 65–90, and 94–126.
    207 pub const RFC5322_ATEXT: AllowedAscii<[u8; 81]> = AllowedAscii {
    208     allowed: [
    209         b'!', b'#', b'$', b'%', b'&', b'\'', b'*', b'+', b'-', b'/', b'0', b'1', b'2', b'3', b'4',
    210         b'5', b'6', b'7', b'8', b'9', b'=', b'?', b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H',
    211         b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W',
    212         b'X', b'Y', b'Z', b'^', b'_', b'`', b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i',
    213         b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x',
    214         b'y', b'z', b'{', b'|', b'}', b'~',
    215     ],
    216 };
    217 /// ASCII allowed in a domain by [Firefox](https://www.mozilla.org/en-US/firefox/)
    218 /// as of 2023-09-03T20:50+00:00.
    219 /// This contains the following `u8`s:
    220 ///
    221 /// 33, 36, 38–41, 43–45, 48–57, 59, 61, 65–90, 95–123, and 125–126.
    222 pub const ASCII_FIREFOX: AllowedAscii<[u8; 78]> = AllowedAscii {
    223     allowed: [
    224         b'!', b'$', b'&', b'\'', b'(', b')', b'+', b',', b'-', b'0', b'1', b'2', b'3', b'4', b'5',
    225         b'6', b'7', b'8', b'9', b';', b'=', b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I',
    226         b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X',
    227         b'Y', b'Z', b'_', b'`', b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k',
    228         b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z',
    229         b'{', b'}', b'~',
    230     ],
    231 };
    232 /// ASCII hyphen, digits, and letters.
    233 /// This contains 45 and all `u8`s inclusively between 48 and 57, 65 and 90, and 97 and 122.
    234 pub const ASCII_HYPHEN_DIGITS_LETTERS: AllowedAscii<[u8; 63]> = AllowedAscii {
    235     allowed: [
    236         b'-', b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'A', b'B', b'C', b'D',
    237         b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S',
    238         b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h',
    239         b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w',
    240         b'x', b'y', b'z',
    241     ],
    242 };
    243 /// ASCII digits and letters.
    244 /// This contains all `u8`s inclusively between 48 and 57, 65 and 90, and 97 and 122.
    245 pub const ASCII_DIGITS_LETTERS: AllowedAscii<[u8; 62]> = AllowedAscii {
    246     allowed: [
    247         b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'A', b'B', b'C', b'D', b'E',
    248         b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T',
    249         b'U', b'V', b'W', b'X', b'Y', b'Z', b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i',
    250         b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x',
    251         b'y', b'z',
    252     ],
    253 };
    254 /// ASCII letters.
    255 /// This contains all `u8`s inclusively between 65 and 90 and 97 and 122.
    256 pub const ASCII_LETTERS: AllowedAscii<[u8; 52]> = AllowedAscii {
    257     allowed: [
    258         b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O',
    259         b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', b'a', b'b', b'c', b'd',
    260         b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's',
    261         b't', b'u', b'v', b'w', b'x', b'y', b'z',
    262     ],
    263 };
    264 /// ASCII hyphen, digits, and uppercase letters.
    265 /// This contains 45 and all `u8`s inclusively between 48 and 57 and 65 and 90.
    266 pub const ASCII_HYPHEN_DIGITS_UPPERCASE: AllowedAscii<[u8; 37]> = AllowedAscii {
    267     allowed: [
    268         b'-', b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'A', b'B', b'C', b'D',
    269         b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S',
    270         b'T', b'U', b'V', b'W', b'X', b'Y', b'Z',
    271     ],
    272 };
    273 /// ASCII hyphen, digits, and lowercase letters.
    274 /// This contains 45 and all `u8`s inclusively between 48 and 57 and 97 and 122.
    275 pub const ASCII_HYPHEN_DIGITS_LOWERCASE: AllowedAscii<[u8; 37]> = AllowedAscii {
    276     allowed: [
    277         b'-', b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b', b'c', b'd',
    278         b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's',
    279         b't', b'u', b'v', b'w', b'x', b'y', b'z',
    280     ],
    281 };
    282 /// ASCII digits and uppercase letters.
    283 /// This contains all `u8`s inclusively between 48 and 57 and 65 and 90.
    284 pub const ASCII_DIGITS_UPPERCASE: AllowedAscii<[u8; 36]> = AllowedAscii {
    285     allowed: [
    286         b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'A', b'B', b'C', b'D', b'E',
    287         b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T',
    288         b'U', b'V', b'W', b'X', b'Y', b'Z',
    289     ],
    290 };
    291 /// ASCII digits and lowercase letters.
    292 /// This contains all `u8`s inclusively between 48 and 57 and 97 and 122.
    293 pub const ASCII_DIGITS_LOWERCASE: AllowedAscii<[u8; 36]> = AllowedAscii {
    294     allowed: [
    295         b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b', b'c', b'd', b'e',
    296         b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't',
    297         b'u', b'v', b'w', b'x', b'y', b'z',
    298     ],
    299 };
    300 /// ASCII uppercase letters.
    301 /// This contains all `u8`s inclusively between 65 and 90.
    302 pub const ASCII_UPPERCASE: AllowedAscii<[u8; 26]> = AllowedAscii {
    303     allowed: [
    304         b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O',
    305         b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z',
    306     ],
    307 };
    308 /// ASCII lowercase letters.
    309 /// This contains all `u8`s inclusively between 97 and 122.
    310 pub const ASCII_LOWERCASE: AllowedAscii<[u8; 26]> = AllowedAscii {
    311     allowed: [
    312         b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o',
    313         b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z',
    314     ],
    315 };
    316 /// ASCII digits.
    317 /// This contains all `u8`s inclusively between 48 and 57.
    318 pub const ASCII_DIGITS: AllowedAscii<[u8; 10]> = AllowedAscii {
    319     allowed: [b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9'],
    320 };
    321 /// ASCII that is not a [forbidden domain code point](https://url.spec.whatwg.org/#forbidden-domain-code-point).
    322 ///
    323 /// This contains the following `u8`s:
    324 ///
    325 /// 33, 34, 36, 38–45, 48–57, 59, 61, 65–90, 95–123, and 125–126
    326 pub const WHATWG_VALID_DOMAIN_CODE_POINTS: AllowedAscii<[u8; 80]> = AllowedAscii {
    327     allowed: [
    328         b'!', b'"', b'$', b'&', b'\'', b'(', b')', b'*', b'+', b',', b'-', b'0', b'1', b'2', b'3',
    329         b'4', b'5', b'6', b'7', b'8', b'9', b';', b'=', b'A', b'B', b'C', b'D', b'E', b'F', b'G',
    330         b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T', b'U', b'V',
    331         b'W', b'X', b'Y', b'Z', b'_', b'`', b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i',
    332         b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x',
    333         b'y', b'z', b'{', b'}', b'~',
    334     ],
    335 };
    336 #[cfg(test)]
    337 mod tests {
    338     extern crate alloc;
    339     use crate::char_set::{
    340         ASCII_DIGITS, ASCII_DIGITS_LETTERS, ASCII_DIGITS_LOWERCASE, ASCII_DIGITS_UPPERCASE,
    341         ASCII_FIREFOX, ASCII_HYPHEN_DIGITS_LETTERS, ASCII_HYPHEN_DIGITS_LOWERCASE,
    342         ASCII_HYPHEN_DIGITS_UPPERCASE, ASCII_LETTERS, ASCII_LOWERCASE, ASCII_UPPERCASE,
    343         AllowedAscii, AsciiErr, PRINTABLE_ASCII, RFC5322_ATEXT, WHATWG_VALID_DOMAIN_CODE_POINTS,
    344     };
    345     use alloc::{borrow::ToOwned, vec::Vec};
    346     #[test]
    347     fn try_from() {
    348         // Empty is allowed.
    349         assert!(AllowedAscii::try_from_unique_ascii([]).is_ok());
    350         // Duplicates are not allowed.
    351         assert!(
    352             AllowedAscii::try_from_unique_ascii(b"aba".to_owned())
    353                 .map_or_else(|e| e == AsciiErr::Duplicate(b'a'), |_| false)
    354         );
    355         // `b'.'` is not allowed.
    356         assert!(
    357             AllowedAscii::try_from_unique_ascii(b"a.c".to_owned())
    358                 .map_or_else(|e| e == AsciiErr::Contains46, |_| false)
    359         );
    360         // At most 127 bytes are allowed.
    361         assert!(
    362             AllowedAscii::try_from_unique_ascii([0; 128])
    363                 .map_or_else(|e| e == AsciiErr::CountTooLarge(128), |_| false)
    364         );
    365         let mut all_ascii = (0..b'.').collect::<Vec<u8>>();
    366         let next = b'.' + 1;
    367         all_ascii.extend(next..=127);
    368         assert!(AllowedAscii::try_from_unique_ascii(all_ascii).is_ok());
    369         // Only ASCII is allowed.
    370         assert!(
    371             AllowedAscii::try_from_unique_ascii([255])
    372                 .map_or_else(|e| e == AsciiErr::InvalidByte(255), |_| false)
    373         );
    374         assert!(
    375             AllowedAscii::try_from_unique_ascii(b"abcdef".to_owned()).map_or(false, |bytes| bytes
    376                 .contains(b'a')
    377                 && bytes.contains(b'b')
    378                 && bytes.contains(b'c')
    379                 && bytes.contains(b'd')
    380                 && bytes.contains(b'e')
    381                 && bytes.contains(b'f'))
    382         );
    383     }
    384     #[test]
    385     fn test_consts() {
    386         let letters = ASCII_LETTERS;
    387         assert!(letters.len() == 52);
    388         for i in b'A'..=b'Z' {
    389             assert!(letters.contains(i));
    390         }
    391         for i in b'a'..=b'z' {
    392             assert!(letters.contains(i));
    393         }
    394         let digits = ASCII_DIGITS;
    395         assert!(digits.len() == 10);
    396         for i in b'0'..=b'9' {
    397             assert!(digits.contains(i));
    398         }
    399         let lower = ASCII_LOWERCASE;
    400         assert!(lower.len() == 26);
    401         for i in b'a'..=b'z' {
    402             assert!(lower.contains(i));
    403         }
    404         let upper = ASCII_UPPERCASE;
    405         assert!(upper.len() == 26);
    406         for i in b'A'..=b'Z' {
    407             assert!(upper.contains(i));
    408         }
    409         let dig_let = ASCII_DIGITS_LETTERS;
    410         assert!(dig_let.len() == 62);
    411         for i in b'a'..=b'z' {
    412             assert!(dig_let.contains(i));
    413         }
    414         for i in b'0'..=b'9' {
    415             assert!(dig_let.contains(i));
    416         }
    417         for i in b'A'..=b'Z' {
    418             assert!(dig_let.contains(i));
    419         }
    420         let dig_lower = ASCII_DIGITS_LOWERCASE;
    421         assert!(dig_lower.len() == 36);
    422         for i in b'a'..=b'z' {
    423             assert!(dig_lower.contains(i));
    424         }
    425         for i in b'0'..=b'9' {
    426             assert!(dig_lower.contains(i));
    427         }
    428         let dig_upper = ASCII_DIGITS_UPPERCASE;
    429         assert!(dig_upper.len() == 36);
    430         for i in b'A'..=b'Z' {
    431             assert!(dig_upper.contains(i));
    432         }
    433         for i in b'0'..=b'9' {
    434             assert!(dig_upper.contains(i));
    435         }
    436         let ffox = ASCII_FIREFOX;
    437         assert!(ffox.len() == 78);
    438         for i in b'A'..=b'Z' {
    439             assert!(ffox.contains(i));
    440         }
    441         for i in b'a'..=b'z' {
    442             assert!(ffox.contains(i));
    443         }
    444         for i in b'0'..=b'9' {
    445             assert!(ffox.contains(i));
    446         }
    447         assert!(ffox.contains(b'!'));
    448         assert!(ffox.contains(b'$'));
    449         assert!(ffox.contains(b'&'));
    450         assert!(ffox.contains(b'\''));
    451         assert!(ffox.contains(b'('));
    452         assert!(ffox.contains(b')'));
    453         assert!(ffox.contains(b'+'));
    454         assert!(ffox.contains(b','));
    455         assert!(ffox.contains(b'-'));
    456         assert!(ffox.contains(b';'));
    457         assert!(ffox.contains(b'='));
    458         assert!(ffox.contains(b'_'));
    459         assert!(ffox.contains(b'`'));
    460         assert!(ffox.contains(b'{'));
    461         assert!(ffox.contains(b'}'));
    462         assert!(ffox.contains(b'~'));
    463         assert!(ASCII_HYPHEN_DIGITS_LETTERS.len() == 63);
    464         assert!(ASCII_HYPHEN_DIGITS_LETTERS.contains(b'-'));
    465         for i in b'A'..=b'Z' {
    466             assert!(ASCII_HYPHEN_DIGITS_LETTERS.contains(i));
    467         }
    468         for i in b'a'..=b'z' {
    469             assert!(ASCII_HYPHEN_DIGITS_LETTERS.contains(i));
    470         }
    471         for i in b'0'..=b'9' {
    472             assert!(ASCII_HYPHEN_DIGITS_LETTERS.contains(i));
    473         }
    474         let hyp_lower = ASCII_HYPHEN_DIGITS_LOWERCASE;
    475         assert!(hyp_lower.len() == 37);
    476         assert!(hyp_lower.contains(b'-'));
    477         for i in b'a'..=b'z' {
    478             assert!(hyp_lower.contains(i));
    479         }
    480         for i in b'0'..=b'9' {
    481             assert!(hyp_lower.contains(i));
    482         }
    483         let hyp_upper = ASCII_HYPHEN_DIGITS_UPPERCASE;
    484         assert!(hyp_upper.len() == 37);
    485         assert!(hyp_upper.contains(b'-'));
    486         for i in b'A'..=b'Z' {
    487             assert!(hyp_upper.contains(i));
    488         }
    489         for i in b'0'..=b'9' {
    490             assert!(hyp_upper.contains(i));
    491         }
    492         let printable = PRINTABLE_ASCII;
    493         assert!(printable.len() == 92);
    494         let stop = b'.' - 1;
    495         for i in 33..=stop {
    496             assert!(printable.contains(i));
    497         }
    498         let stop2 = b'\\' - 1;
    499         for i in stop + 2..=stop2 {
    500             assert!(printable.contains(i));
    501         }
    502         for i in stop2 + 2..=b'~' {
    503             assert!(printable.contains(i));
    504         }
    505         let rfc = RFC5322_ATEXT;
    506         assert!(rfc.len() == 81);
    507         for i in b'A'..=b'Z' {
    508             assert!(rfc.contains(i));
    509         }
    510         for i in b'a'..=b'z' {
    511             assert!(rfc.contains(i));
    512         }
    513         for i in b'0'..=b'9' {
    514             assert!(rfc.contains(i));
    515         }
    516         assert!(rfc.contains(b'!'));
    517         assert!(rfc.contains(b'#'));
    518         assert!(rfc.contains(b'$'));
    519         assert!(rfc.contains(b'%'));
    520         assert!(rfc.contains(b'&'));
    521         assert!(rfc.contains(b'\''));
    522         assert!(rfc.contains(b'*'));
    523         assert!(rfc.contains(b'+'));
    524         assert!(rfc.contains(b'-'));
    525         assert!(rfc.contains(b'/'));
    526         assert!(rfc.contains(b'='));
    527         assert!(rfc.contains(b'?'));
    528         assert!(rfc.contains(b'^'));
    529         assert!(rfc.contains(b'_'));
    530         assert!(rfc.contains(b'`'));
    531         assert!(rfc.contains(b'{'));
    532         assert!(rfc.contains(b'|'));
    533         assert!(rfc.contains(b'}'));
    534         assert!(rfc.contains(b'~'));
    535         let whatwg = WHATWG_VALID_DOMAIN_CODE_POINTS;
    536         for i in 0..=0x1f {
    537             assert!(!whatwg.contains(i));
    538         }
    539         assert!(!whatwg.contains(b'\x20'));
    540         assert!(!whatwg.contains(b'#'));
    541         assert!(!whatwg.contains(b'/'));
    542         assert!(!whatwg.contains(b':'));
    543         assert!(!whatwg.contains(b'<'));
    544         assert!(!whatwg.contains(b'>'));
    545         assert!(!whatwg.contains(b'?'));
    546         assert!(!whatwg.contains(b'@'));
    547         assert!(!whatwg.contains(b'['));
    548         assert!(!whatwg.contains(b'\\'));
    549         assert!(!whatwg.contains(b']'));
    550         assert!(!whatwg.contains(b'^'));
    551         assert!(!whatwg.contains(b'|'));
    552         assert!(!whatwg.contains(b'%'));
    553         assert!(!whatwg.contains(b'\x7f'));
    554         assert!(!whatwg.contains(b'.'));
    555         assert!(whatwg.len() == 128 - 32 - 16);
    556     }
    557 }