iri_string/parser/validate/
authority.rs

1//! Parsers for authority.
2
3use 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
13/// Returns `Ok(_)` if the string matches `userinfo` or `iuserinfo`.
14pub(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/// Returns `true` if the string matches `dec-octet`.
28///
29/// In other words, this tests whether the string is decimal "0" to "255".
30#[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
42/// Returns `Ok(_)` if the string matches `IPv4address`.
43fn 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/// A part of IPv6 addr.
61#[derive(Clone, Copy)]
62enum V6AddrPart {
63    /// `[0-9a-fA-F]{1,4}::`.
64    H16Omit,
65    /// `[0-9a-fA-F]{1,4}:`.
66    H16Cont,
67    /// `[0-9a-fA-F]{1,4}`.
68    H16End,
69    /// IPv4 address.
70    V4,
71    /// `::`.
72    Omit,
73}
74
75/// Splits the IPv6 address string into the next component and the rest substring.
76fn 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            // Should be `h16`.
92            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                // Possibly `IPv4address`.
104                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
116/// Returns `Ok(_)` if the string matches `IPv6address`.
117fn 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                    // Omitted twice.
127                    return Err(Error::new());
128                }
129            }
130            V6AddrPart::H16Cont => {
131                h16_count += 1;
132                if rest.is_empty() {
133                    // `H16Cont` cannot be the last part of an IPv6 address.
134                    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                    // Omitted twice.
149                    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
170/// Returns `Ok(_)` if the string matches `authority` or `iauthority`.
171pub(super) fn validate_authority<S: Spec>(i: &str) -> Result<(), Error> {
172    // Strip and validate `userinfo`.
173    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    // `host` can contain colons, but `port` cannot.
181    // Strip and validate `port`.
182    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`.
193    validate_host::<S>(maybe_host)
194}
195
196/// Validates `host`.
197pub(crate) fn validate_host<S: Spec>(i: &str) -> Result<(), Error> {
198    match get_wrapped_inner(i, b'[', b']') {
199        Some(maybe_addr) => {
200            // `IP-literal`.
201            // Note that `v` here is case insensitive. See RFC 3987 section 3.2.2.
202            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                // `IPvFuture`.
206                let (maybe_ver, maybe_addr) =
207                    find_split_hole(maybe_addr_rest, b'.').ok_or_else(Error::new)?;
208                // Validate version.
209                if maybe_ver.is_empty() || !maybe_ver.bytes().all(|b| b.is_ascii_hexdigit()) {
210                    return Err(Error::new());
211                }
212                // Validate address.
213                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                // `IPv6address`.
225                validate_ipv6address(maybe_addr)
226            }
227        }
228        None => {
229            // `IPv4address` or `reg-name`. No need to distinguish them here.
230            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        // Generate IPv6 addresses with `::`.
271        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}