web_url/parse/pre_path/
pre_path.rs

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