web_url/parse/pre_path/
pre_path.rs

1use address::IPAddress;
2
3use crate::parse::pre_path::parse_port;
4use crate::parse::pre_path::parse_scheme_len;
5use crate::parse::pre_path::{parse_host, parse_ip_and_validate_domain};
6use crate::parse::Error;
7
8/// The parsing data for a web-based URL before the path.
9#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
10pub struct PrePath {
11    pub scheme_len: usize,
12    pub host_len: usize,
13    pub ip: Option<IPAddress>,
14    pub port: Option<u16>,
15    pub port_len: usize,
16}
17
18impl PrePath {
19    //! Properties
20
21    /// Gets the length of the pre-path string.
22    pub fn len(&self) -> usize {
23        self.scheme_len + 3 + self.host_len + self.port_len
24    }
25
26    /// Checks if the pre-path string is empty.
27    pub fn is_empty(&self) -> bool {
28        false
29    }
30}
31
32impl PrePath {
33    //! Operations
34
35    /// Makes the pre-path prefix of `s` lowercase.
36    ///
37    /// # Safety
38    /// This requires the string up to `len` to be US-ASCII. This will be true if it was parsed
39    /// with the `parse_pre_path` function.
40    pub unsafe fn make_lowercase(&self, url: &mut str) {
41        url[..self.len()]
42            .as_bytes_mut()
43            .iter_mut()
44            .for_each(|c| *c = c.to_ascii_lowercase())
45    }
46}
47
48/// Parses the pre-path portion of the URL.
49/// The scheme & host will be validated but may be uppercase.
50///
51/// Returns `Ok(pre_path)`.
52/// Returns `Err(_)` if any part of the pre-path is invalid.
53pub fn parse_pre_path(url: &str) -> Result<PrePath, Error> {
54    let (scheme_len, after_scheme) = parse_scheme_len(url)?;
55    let (host_str, after_host) = parse_host(after_scheme);
56    let ip: Option<IPAddress> = parse_ip_and_validate_domain(host_str)?;
57    let (port, after_port) = parse_port(after_host)?;
58    let port_len: usize = after_host.len() - after_port.len();
59    let pre_path: PrePath = PrePath {
60        scheme_len,
61        host_len: host_str.len(),
62        ip,
63        port,
64        port_len,
65    };
66    Ok(pre_path)
67}
68
69#[cfg(test)]
70mod tests {
71    use address::{IPv4Address, IPv6Address};
72
73    use crate::parse::pre_path::{parse_pre_path, PrePath};
74    use crate::parse::Error;
75    use crate::parse::Error::{InvalidHost, InvalidScheme};
76
77    #[test]
78    fn fn_parse_pre_path() {
79        let test_cases: &[(&str, Result<PrePath, Error>)] = &[
80            ("scheme:/", Err(InvalidScheme)),
81            ("!://", Err(InvalidScheme)),
82            ("scheme://", Err(InvalidHost)),
83            (
84                "scheme://host",
85                Ok(PrePath {
86                    scheme_len: 6,
87                    host_len: 4,
88                    ip: None,
89                    port: None,
90                    port_len: 0,
91                }),
92            ),
93            (
94                "scheme://127.0.0.1",
95                Ok(PrePath {
96                    scheme_len: 6,
97                    host_len: 9,
98                    ip: Some(IPv4Address::LOCALHOST.to_ip()),
99                    port: None,
100                    port_len: 0,
101                }),
102            ),
103            ("scheme://::1", Err(InvalidHost)),
104            (
105                "scheme://[::1]",
106                Ok(PrePath {
107                    scheme_len: 6,
108                    host_len: 5,
109                    ip: Some(IPv6Address::LOCALHOST.to_ip()),
110                    port: None,
111                    port_len: 0,
112                }),
113            ),
114            (
115                "scheme://[::1]:80",
116                Ok(PrePath {
117                    scheme_len: 6,
118                    host_len: 5,
119                    ip: Some(IPv6Address::LOCALHOST.to_ip()),
120                    port: Some(80),
121                    port_len: 3,
122                }),
123            ),
124            (
125                "scheme://[::1]:80/the/path",
126                Ok(PrePath {
127                    scheme_len: 6,
128                    host_len: 5,
129                    ip: Some(IPv6Address::LOCALHOST.to_ip()),
130                    port: Some(80),
131                    port_len: 3,
132                }),
133            ),
134        ];
135        for (input, expected) in test_cases {
136            let result: Result<PrePath, Error> = parse_pre_path(input);
137            assert_eq!(result, *expected, "input={}", input);
138        }
139    }
140}