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, ErrorKind};
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::with_kind(ErrorKind::InvalidUserInfo))
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    /// Returns `Ok(_)` if the string matches `IPv4address`, or `Err(())` if not.
45    fn validate_ipv4address_impl(i: &str) -> Result<(), ()> {
46        let (first, rest) = find_split_hole(i, b'.').ok_or(())?;
47        if !is_dec_octet(first) {
48            return Err(());
49        }
50        let (second, rest) = find_split_hole(rest, b'.').ok_or(())?;
51        if !is_dec_octet(second) {
52            return Err(());
53        }
54        let (third, fourth) = find_split_hole(rest, b'.').ok_or(())?;
55        if is_dec_octet(third) && is_dec_octet(fourth) {
56            Ok(())
57        } else {
58            Err(())
59        }
60    }
61
62    validate_ipv4address_impl(i).map_err(|_| Error::with_kind(ErrorKind::InvalidHost))
63}
64
65/// A part of IPv6 addr.
66#[derive(Clone, Copy)]
67enum V6AddrPart {
68    /// `[0-9a-fA-F]{1,4}::`.
69    H16Omit,
70    /// `[0-9a-fA-F]{1,4}:`.
71    H16Cont,
72    /// `[0-9a-fA-F]{1,4}`.
73    H16End,
74    /// IPv4 address.
75    V4,
76    /// `::`.
77    Omit,
78}
79
80/// Splits the IPv6 address string into the next component and the rest substring.
81fn split_v6_addr_part(i: &str) -> Result<(&str, V6AddrPart), Error> {
82    debug_assert!(!i.is_empty());
83    match find_split_hole(i, b':') {
84        Some((prefix, rest)) => {
85            if prefix.len() >= 5 {
86                return Err(Error::with_kind(ErrorKind::InvalidHost));
87            }
88
89            if prefix.is_empty() {
90                return match strip_ascii_char_prefix(rest, b':') {
91                    Some(rest) => Ok((rest, V6AddrPart::Omit)),
92                    None => Err(Error::with_kind(ErrorKind::InvalidHost)),
93                };
94            }
95
96            // Should be `h16`.
97            debug_assert!((1..=4).contains(&prefix.len()));
98            if !prefix.bytes().all(|b| b.is_ascii_hexdigit()) {
99                return Err(Error::with_kind(ErrorKind::InvalidHost));
100            }
101            match strip_ascii_char_prefix(rest, b':') {
102                Some(rest) => Ok((rest, V6AddrPart::H16Omit)),
103                None => Ok((rest, V6AddrPart::H16Cont)),
104            }
105        }
106        None => {
107            if i.len() >= 5 {
108                // Possibly `IPv4address`.
109                validate_ipv4address(i)?;
110                return Ok(("", V6AddrPart::V4));
111            }
112            if i.bytes().all(|b| b.is_ascii_hexdigit()) {
113                Ok(("", V6AddrPart::H16End))
114            } else {
115                Err(Error::with_kind(ErrorKind::InvalidHost))
116            }
117        }
118    }
119}
120
121/// Returns `Ok(_)` if the string matches `IPv6address`.
122fn validate_ipv6address(mut i: &str) -> Result<(), Error> {
123    let mut h16_count = 0;
124    let mut is_omitted = false;
125    while !i.is_empty() {
126        let (rest, part) = split_v6_addr_part(i)?;
127        match part {
128            V6AddrPart::H16Omit => {
129                h16_count += 1;
130                if mem::replace(&mut is_omitted, true) {
131                    // Omitted twice.
132                    return Err(Error::with_kind(ErrorKind::InvalidHost));
133                }
134            }
135            V6AddrPart::H16Cont => {
136                h16_count += 1;
137                if rest.is_empty() {
138                    // `H16Cont` cannot be the last part of an IPv6 address.
139                    return Err(Error::with_kind(ErrorKind::InvalidHost));
140                }
141            }
142            V6AddrPart::H16End => {
143                h16_count += 1;
144                break;
145            }
146            V6AddrPart::V4 => {
147                debug_assert!(rest.is_empty());
148                h16_count += 2;
149                break;
150            }
151            V6AddrPart::Omit => {
152                if mem::replace(&mut is_omitted, true) {
153                    // Omitted twice.
154                    return Err(Error::with_kind(ErrorKind::InvalidHost));
155                }
156            }
157        }
158        if h16_count > 8 {
159            return Err(Error::with_kind(ErrorKind::InvalidHost));
160        }
161        i = rest;
162    }
163    let is_valid = if is_omitted {
164        h16_count < 8
165    } else {
166        h16_count == 8
167    };
168    if is_valid {
169        Ok(())
170    } else {
171        Err(Error::with_kind(ErrorKind::InvalidHost))
172    }
173}
174
175/// Returns `Ok(_)` if the string matches `authority` or `iauthority`.
176pub(crate) fn validate_authority<S: Spec>(i: &str) -> Result<(), Error> {
177    // Strip and validate `userinfo`.
178    let (i, _userinfo) = match find_split_hole(i, b'@') {
179        Some((maybe_userinfo, i)) => {
180            validate_userinfo::<S>(maybe_userinfo)?;
181            (i, Some(maybe_userinfo))
182        }
183        None => (i, None),
184    };
185    // `host` can contain colons, but `port` cannot.
186    // Strip and validate `port`.
187    let (maybe_host, _port) = match rfind_split_hole(i, b':') {
188        Some((maybe_host, maybe_port)) => {
189            if maybe_port.bytes().all(|b| b.is_ascii_digit()) {
190                (maybe_host, Some(maybe_port))
191            } else {
192                (i, None)
193            }
194        }
195        None => (i, None),
196    };
197    // Validate `host`.
198    validate_host::<S>(maybe_host)
199}
200
201/// Validates `host`.
202pub(crate) fn validate_host<S: Spec>(i: &str) -> Result<(), Error> {
203    match get_wrapped_inner(i, b'[', b']') {
204        Some(maybe_addr) => {
205            // `IP-literal`.
206            // Note that `v` here is case insensitive. See RFC 3987 section 3.2.2.
207            if let Some(maybe_addr_rest) = strip_ascii_char_prefix(maybe_addr, b'v')
208                .or_else(|| strip_ascii_char_prefix(maybe_addr, b'V'))
209            {
210                // `IPvFuture`.
211                let (maybe_ver, maybe_addr) = find_split_hole(maybe_addr_rest, b'.')
212                    .ok_or(Error::with_kind(ErrorKind::InvalidHost))?;
213                // Validate version.
214                if maybe_ver.is_empty() || !maybe_ver.bytes().all(|b| b.is_ascii_hexdigit()) {
215                    return Err(Error::with_kind(ErrorKind::InvalidHost));
216                }
217                // Validate address.
218                if !maybe_addr.is_empty()
219                    && maybe_addr.is_ascii()
220                    && maybe_addr
221                        .bytes()
222                        .all(char::is_ascii_userinfo_ipvfutureaddr)
223                {
224                    Ok(())
225                } else {
226                    Err(Error::with_kind(ErrorKind::InvalidHost))
227                }
228            } else {
229                // `IPv6address`.
230                validate_ipv6address(maybe_addr)
231            }
232        }
233        None => {
234            // `IPv4address` or `reg-name`. No need to distinguish them here
235            // because `IPv4address` is also syntactically valid as `reg-name`.
236            let is_valid = satisfy_chars_with_pct_encoded(
237                i,
238                char::is_ascii_regname,
239                char::is_nonascii_regname::<S>,
240            );
241            if is_valid {
242                Ok(())
243            } else {
244                Err(Error::with_kind(ErrorKind::InvalidHost))
245            }
246        }
247    }
248}
249
250#[cfg(test)]
251#[cfg(feature = "alloc")]
252mod tests {
253    use super::*;
254
255    use alloc::format;
256
257    macro_rules! assert_validate {
258        ($parser:expr, $($input:expr),* $(,)?) => {{
259            $({
260                let input = $input;
261                let input: &str = input.as_ref();
262                assert!($parser(input).is_ok(), "input={:?}", input);
263            })*
264        }};
265    }
266
267    #[test]
268    fn test_ipv6address() {
269        use core::cmp::Ordering;
270
271        assert_validate!(validate_ipv6address, "a:bB:cCc:dDdD:e:F:a:B");
272        assert_validate!(validate_ipv6address, "1:1:1:1:1:1:1:1");
273        assert_validate!(validate_ipv6address, "1:1:1:1:1:1:1.1.1.1");
274        assert_validate!(validate_ipv6address, "2001:db8::7");
275
276        // Generate IPv6 addresses with `::`.
277        let make_sub = |n: usize| {
278            let mut s = "1:".repeat(n);
279            s.pop();
280            s
281        };
282        for len_pref in 0..=7 {
283            let prefix = make_sub(len_pref);
284            for len_suf in 1..=(7 - len_pref) {
285                assert_validate!(
286                    validate_ipv6address,
287                    &format!("{}::{}", prefix, make_sub(len_suf))
288                );
289                match len_suf.cmp(&2) {
290                    Ordering::Greater => assert_validate!(
291                        validate_ipv6address,
292                        &format!("{}::{}:1.1.1.1", prefix, make_sub(len_suf - 2))
293                    ),
294                    Ordering::Equal => {
295                        assert_validate!(validate_ipv6address, &format!("{}::1.1.1.1", prefix))
296                    }
297                    Ordering::Less => {}
298                }
299            }
300        }
301    }
302}