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