iri_string/parser/validate/
authority.rs1use core::mem;
4
5use crate::parser::char;
6use crate::parser::str::{
7 find_split_hole, get_wrapped_inner, rfind_split_hole, satisfy_chars_with_pct_encoded,
8 strip_ascii_char_prefix,
9};
10use crate::spec::Spec;
11use crate::validate::Error;
12
13pub(crate) fn validate_userinfo<S: Spec>(i: &str) -> Result<(), Error> {
15 let is_valid = satisfy_chars_with_pct_encoded(
16 i,
17 char::is_ascii_userinfo_ipvfutureaddr,
18 char::is_nonascii_userinfo::<S>,
19 );
20 if is_valid {
21 Ok(())
22 } else {
23 Err(Error::new())
24 }
25}
26
27#[must_use]
31fn is_dec_octet(i: &str) -> bool {
32 matches!(
33 i.as_bytes(),
34 [b'0'..=b'9']
35 | [b'1'..=b'9', b'0'..=b'9']
36 | [b'1', b'0'..=b'9', b'0'..=b'9']
37 | [b'2', b'0'..=b'4', b'0'..=b'9']
38 | [b'2', b'5', b'0'..=b'5']
39 )
40}
41
42fn validate_ipv4address(i: &str) -> Result<(), Error> {
44 let (first, rest) = find_split_hole(i, b'.').ok_or_else(Error::new)?;
45 if !is_dec_octet(first) {
46 return Err(Error::new());
47 }
48 let (second, rest) = find_split_hole(rest, b'.').ok_or_else(Error::new)?;
49 if !is_dec_octet(second) {
50 return Err(Error::new());
51 }
52 let (third, fourth) = find_split_hole(rest, b'.').ok_or_else(Error::new)?;
53 if is_dec_octet(third) && is_dec_octet(fourth) {
54 Ok(())
55 } else {
56 Err(Error::new())
57 }
58}
59
60#[derive(Clone, Copy)]
62enum V6AddrPart {
63 H16Omit,
65 H16Cont,
67 H16End,
69 V4,
71 Omit,
73}
74
75fn split_v6_addr_part(i: &str) -> Result<(&str, V6AddrPart), Error> {
77 debug_assert!(!i.is_empty());
78 match find_split_hole(i, b':') {
79 Some((prefix, rest)) => {
80 if prefix.len() >= 5 {
81 return Err(Error::new());
82 }
83
84 if prefix.is_empty() {
85 return match strip_ascii_char_prefix(rest, b':') {
86 Some(rest) => Ok((rest, V6AddrPart::Omit)),
87 None => Err(Error::new()),
88 };
89 }
90
91 debug_assert!((1..=4).contains(&prefix.len()));
93 if !prefix.bytes().all(|b| b.is_ascii_hexdigit()) {
94 return Err(Error::new());
95 }
96 match strip_ascii_char_prefix(rest, b':') {
97 Some(rest) => Ok((rest, V6AddrPart::H16Omit)),
98 None => Ok((rest, V6AddrPart::H16Cont)),
99 }
100 }
101 None => {
102 if i.len() >= 5 {
103 validate_ipv4address(i)?;
105 return Ok(("", V6AddrPart::V4));
106 }
107 if i.bytes().all(|b| b.is_ascii_hexdigit()) {
108 Ok(("", V6AddrPart::H16End))
109 } else {
110 Err(Error::new())
111 }
112 }
113 }
114}
115
116fn validate_ipv6address(mut i: &str) -> Result<(), Error> {
118 let mut h16_count = 0;
119 let mut is_omitted = false;
120 while !i.is_empty() {
121 let (rest, part) = split_v6_addr_part(i)?;
122 match part {
123 V6AddrPart::H16Omit => {
124 h16_count += 1;
125 if mem::replace(&mut is_omitted, true) {
126 return Err(Error::new());
128 }
129 }
130 V6AddrPart::H16Cont => {
131 h16_count += 1;
132 if rest.is_empty() {
133 return Err(Error::new());
135 }
136 }
137 V6AddrPart::H16End => {
138 h16_count += 1;
139 break;
140 }
141 V6AddrPart::V4 => {
142 debug_assert!(rest.is_empty());
143 h16_count += 2;
144 break;
145 }
146 V6AddrPart::Omit => {
147 if mem::replace(&mut is_omitted, true) {
148 return Err(Error::new());
150 }
151 }
152 }
153 if h16_count > 8 {
154 return Err(Error::new());
155 }
156 i = rest;
157 }
158 let is_valid = if is_omitted {
159 h16_count < 8
160 } else {
161 h16_count == 8
162 };
163 if is_valid {
164 Ok(())
165 } else {
166 Err(Error::new())
167 }
168}
169
170pub(super) fn validate_authority<S: Spec>(i: &str) -> Result<(), Error> {
172 let (i, _userinfo) = match find_split_hole(i, b'@') {
174 Some((maybe_userinfo, i)) => {
175 validate_userinfo::<S>(maybe_userinfo)?;
176 (i, Some(maybe_userinfo))
177 }
178 None => (i, None),
179 };
180 let (maybe_host, _port) = match rfind_split_hole(i, b':') {
183 Some((maybe_host, maybe_port)) => {
184 if maybe_port.bytes().all(|b| b.is_ascii_digit()) {
185 (maybe_host, Some(maybe_port))
186 } else {
187 (i, None)
188 }
189 }
190 None => (i, None),
191 };
192 validate_host::<S>(maybe_host)
194}
195
196pub(crate) fn validate_host<S: Spec>(i: &str) -> Result<(), Error> {
198 match get_wrapped_inner(i, b'[', b']') {
199 Some(maybe_addr) => {
200 if let Some(maybe_addr_rest) = strip_ascii_char_prefix(maybe_addr, b'v')
203 .or_else(|| strip_ascii_char_prefix(maybe_addr, b'V'))
204 {
205 let (maybe_ver, maybe_addr) =
207 find_split_hole(maybe_addr_rest, b'.').ok_or_else(Error::new)?;
208 if maybe_ver.is_empty() || !maybe_ver.bytes().all(|b| b.is_ascii_hexdigit()) {
210 return Err(Error::new());
211 }
212 if !maybe_addr.is_empty()
214 && maybe_addr.is_ascii()
215 && maybe_addr
216 .bytes()
217 .all(char::is_ascii_userinfo_ipvfutureaddr)
218 {
219 Ok(())
220 } else {
221 Err(Error::new())
222 }
223 } else {
224 validate_ipv6address(maybe_addr)
226 }
227 }
228 None => {
229 let is_valid = satisfy_chars_with_pct_encoded(
231 i,
232 char::is_ascii_regname,
233 char::is_nonascii_regname::<S>,
234 );
235 if is_valid {
236 Ok(())
237 } else {
238 Err(Error::new())
239 }
240 }
241 }
242}
243
244#[cfg(test)]
245#[cfg(feature = "alloc")]
246mod tests {
247 use super::*;
248
249 use alloc::format;
250
251 macro_rules! assert_validate {
252 ($parser:expr, $($input:expr),* $(,)?) => {{
253 $({
254 let input = $input;
255 let input: &str = input.as_ref();
256 assert!($parser(input).is_ok(), "input={:?}", input);
257 })*
258 }};
259 }
260
261 #[test]
262 fn test_ipv6address() {
263 use core::cmp::Ordering;
264
265 assert_validate!(validate_ipv6address, "a:bB:cCc:dDdD:e:F:a:B");
266 assert_validate!(validate_ipv6address, "1:1:1:1:1:1:1:1");
267 assert_validate!(validate_ipv6address, "1:1:1:1:1:1:1.1.1.1");
268 assert_validate!(validate_ipv6address, "2001:db8::7");
269
270 let make_sub = |n: usize| {
272 let mut s = "1:".repeat(n);
273 s.pop();
274 s
275 };
276 for len_pref in 0..=7 {
277 let prefix = make_sub(len_pref);
278 for len_suf in 1..=(7 - len_pref) {
279 assert_validate!(
280 validate_ipv6address,
281 &format!("{}::{}", prefix, make_sub(len_suf))
282 );
283 match len_suf.cmp(&2) {
284 Ordering::Greater => assert_validate!(
285 validate_ipv6address,
286 &format!("{}::{}:1.1.1.1", prefix, make_sub(len_suf - 2))
287 ),
288 Ordering::Equal => {
289 assert_validate!(validate_ipv6address, &format!("{}::1.1.1.1", prefix))
290 }
291 Ordering::Less => {}
292 }
293 }
294 }
295 }
296}