1pub fn valid_name_code_point(cp: u32) -> bool {
2 matches!(
3 cp,
4 0x002D | 0x0030..=0x0039 | 0x0061..=0x007A
5 | 0x00B7
6 | 0x00C0..=0x00D6 | 0x00D8..=0x00F6 | 0x00F8..=0x037D
7 | 0x037F..=0x1FFF
8 | 0x200C | 0x200D
9 | 0x203F | 0x2040
10 | 0x2070..=0x218F
11 | 0x2C00..=0x2FEF
12 | 0x3001..=0xD7FF
13 | 0xF900..=0xFDCF
14 | 0xFDF0..=0xFFFD
15 | 0x10000..=0xEFFFF
16 )
17}
18
19pub fn valid_name_code_point_first_position(cp: u32) -> bool {
20 match cp {
22 0x0030..=0x0039 => false, 0x002D => false, _ => is_letter(cp),
25 }
26}
27
28pub fn valid_name_code_point_other_position(cp: u32) -> bool {
29 match cp {
31 0x0030..=0x0039 => true, 0x002D => true, _ => is_letter(cp),
34 }
35}
36
37fn is_letter(cp: u32) -> bool {
38 matches!(
39 cp,
40 0x0041..=0x005A | 0x0061..=0x007A | 0x00C0..=0x00D6 | 0x00D8..=0x00F6 | 0x00F8..=0x00FF | 0x0100..=0x017F | 0x0180..=0x024F | 0x0370..=0x03FF | 0x0400..=0x04FF | 0x0590..=0x05FF | 0x0600..=0x06FF | 0x4E00..=0x9FFF | 0x3040..=0x309F | 0x30A0..=0x30FF | 0xAC00..=0xD7AF | 0x0E00..=0x0E7F )
54}
55
56pub fn contains_forbidden_domain_code_point(input: &str) -> bool {
57 for c in input.chars() {
58 let cp = c as u32;
59 match cp {
60 0x0000..=0x001F | 0x007F..=0x009F => return true,
61 0x0020 | 0x0022 | 0x0023 | 0x0025 | 0x002F => return true,
62 0x003A | 0x003C | 0x003E | 0x003F | 0x0040 => return true,
63 0x005B | 0x005C | 0x005D | 0x005E | 0x007C => return true,
64 _ => continue,
65 }
66 }
67 false
68}
69
70pub fn is_ascii(input: &str) -> bool {
71 input.is_ascii()
72}
73
74pub fn is_label_valid(label: &str) -> bool {
75 if label.is_empty() || label.len() > 63 {
76 return false;
77 }
78
79 if label.starts_with('-') || label.ends_with('-') {
80 return false;
81 }
82
83 if let Some(stripped) = label.strip_prefix("xn--") {
84 return crate::punycode::verify_punycode(stripped);
85 }
86
87 for c in label.chars() {
88 if !valid_name_code_point(c as u32) {
89 return false;
90 }
91 }
92
93 true
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 #[test]
101 fn test_valid_name_code_point() {
102 assert!(valid_name_code_point(b'a' as u32));
103 assert!(valid_name_code_point(b'0' as u32));
104 assert!(valid_name_code_point(b'-' as u32));
105 assert!(!valid_name_code_point(b' ' as u32));
106 }
107
108 #[test]
109 fn test_is_ascii() {
110 assert!(is_ascii("hello"));
111 assert!(!is_ascii("café"));
112 }
113
114 #[test]
115 fn test_is_label_valid() {
116 assert!(is_label_valid("hello"));
117 assert!(is_label_valid("test-domain"));
118 assert!(!is_label_valid("-invalid"));
119 assert!(!is_label_valid("invalid-"));
120 assert!(!is_label_valid(""));
121 }
122
123 #[test]
124 fn test_contains_forbidden_domain_code_point() {
125 assert!(!contains_forbidden_domain_code_point("example.com"));
126 assert!(contains_forbidden_domain_code_point("exam ple.com"));
127 assert!(contains_forbidden_domain_code_point("example.com/path"));
128 }
129}