Skip to main content

web_url/parse/pre_path/
pre_path.rs

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