Skip to main content

sark_http/uri/
mod.rs

1mod error;
2mod host;
3mod pair;
4mod parser;
5pub mod path;
6mod scheme;
7pub mod target;
8
9use core::net;
10
11pub use error::Error;
12pub use host::Host;
13pub use scheme::Scheme;
14
15use crate::field::metadata::map::record;
16
17/// An admitted decimal port outside the transport's `u16` domain.
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub struct PortError;
20
21#[derive(Clone, Copy, PartialEq, Eq)]
22pub struct Authority<'a> {
23    host: &'a [u8],
24    port: Option<&'a [u8]>,
25}
26
27impl<'a> Authority<'a> {
28    pub(crate) const fn new(host: &'a [u8], port: Option<&'a [u8]>) -> Self {
29        Self { host, port }
30    }
31
32    pub fn parse(value: &'a [u8]) -> Result<Self, Error> {
33        let parts = Parts::<&[u8]>::parse(value)?;
34        Ok(Self {
35            host: parts.host,
36            port: parts.port,
37        })
38    }
39
40    pub fn is_same(self, other: Self, scheme: Scheme) -> bool {
41        self.spanned().is_same(other.spanned(), scheme)
42    }
43
44    pub fn compare_span(self, other: record::Span<'_>, scheme: Scheme) -> Result<bool, Error> {
45        Ok(self.spanned().is_same(Parsed::parse(other)?, scheme))
46    }
47
48    pub(crate) fn spanned(self) -> Parsed<'a> {
49        Parsed(Parts {
50            host: record::Span::from_slice(self.host),
51            port: self.port.map(record::Span::from_slice),
52        })
53    }
54
55    pub const fn host(self) -> &'a [u8] {
56        self.host
57    }
58
59    pub fn hostname(self) -> &'a [u8] {
60        if let Some(host) = self
61            .host
62            .strip_prefix(b"[")
63            .and_then(|host| host.strip_suffix(b"]"))
64        {
65            host
66        } else {
67            self.host
68        }
69    }
70
71    pub const fn port(self) -> Option<&'a [u8]> {
72        self.port
73    }
74
75    /// Converts an admitted decimal port without repeating its syntax check.
76    pub fn try_port_u16(self) -> Result<Option<u16>, PortError> {
77        let Some(port) = self.port.filter(|port| !port.is_empty()) else {
78            return Ok(None);
79        };
80        let mut value = 0u16;
81        for byte in port {
82            value = value
83                .checked_mul(10)
84                .and_then(|value| value.checked_add(u16::from(*byte - b'0')))
85                .ok_or(PortError)?;
86        }
87        Ok(Some(value))
88    }
89
90    pub fn parse_ip(value: &[u8]) -> Result<net::IpAddr, Host> {
91        if let Some(value) = value.strip_prefix(b"[") {
92            let Some(value) = value.strip_suffix(b"]") else {
93                return Err(Host::Invalid);
94            };
95            let value = record::Span::from_slice(value);
96            if is_future_span(value) {
97                return Err(Host::Future);
98            }
99            return parser::Parser::new(value)
100                .v6()
101                .map(net::IpAddr::V6)
102                .ok_or(Host::Invalid);
103        }
104        let span = record::Span::from_slice(value);
105        if let Some(ip) = parser::Parser::new(span).v4().map(net::IpAddr::V4) {
106            return Ok(ip);
107        }
108        if let Some(ip) = parser::Parser::new(span).v6().map(net::IpAddr::V6) {
109            return Ok(ip);
110        }
111        if Self::is_reg(value) {
112            Err(Host::Name)
113        } else {
114            Err(Host::Invalid)
115        }
116    }
117
118    pub(crate) fn is_port(value: &[u8]) -> bool {
119        value
120            .strip_prefix(b":")
121            .is_some_and(|port| !port.is_empty() && port.iter().all(u8::is_ascii_digit))
122    }
123
124    pub(crate) fn is_reg(value: &[u8]) -> bool {
125        is_reg_span(record::Span::from_slice(value))
126    }
127
128    pub(crate) fn is_ip(value: &[u8]) -> bool {
129        let value = record::Span::from_slice(value);
130        is_future_span(value) || parser::Parser::new(value).v6().is_some()
131    }
132}
133
134struct Parts<S> {
135    host: S,
136    port: Option<S>,
137}
138
139/// Authority admission retained on its exact immutable record span.
140pub(crate) struct Parsed<'a>(Parts<record::Span<'a>>);
141
142impl<'a> Parsed<'a> {
143    pub(crate) fn parse(value: record::Span<'a>) -> Result<Self, Error> {
144        Parts::parse(value).map(Self)
145    }
146
147    /// Whether this admitted authority has a nonempty explicit decimal port.
148    pub(crate) fn has_port(self) -> bool {
149        self.0.port.is_some_and(|port| !port.is_empty())
150    }
151
152    pub(crate) fn is_same(&self, other: Parsed<'_>, scheme: Scheme) -> bool {
153        pair::is_same(self.0.host, other.0.host)
154            && is_port_span_equivalent(self.0.port, other.0.port, scheme)
155    }
156}
157
158/// Borrowed URI bytes: one contiguous slice or one two-segment record
159/// span, whose geometry [`Source::sub`] proves once for every later use.
160trait Source<'a>: Copy {
161    fn span(self) -> record::Span<'a>;
162
163    /// The bytes in `start..end`, or `None` when that range is not inside `self`.
164    fn sub(self, start: usize, end: usize) -> Option<Self>;
165
166    fn len(self) -> usize {
167        self.span().len()
168    }
169
170    fn get(self, index: usize) -> Option<u8> {
171        self.span().get(index)
172    }
173}
174
175impl<'a> Source<'a> for &'a [u8] {
176    fn span(self) -> record::Span<'a> {
177        record::Span::from_slice(self)
178    }
179
180    fn sub(self, start: usize, end: usize) -> Option<Self> {
181        self.get(start..end)
182    }
183}
184
185impl<'a> Source<'a> for record::Span<'a> {
186    fn span(self) -> record::Span<'a> {
187        self
188    }
189
190    fn sub(self, start: usize, end: usize) -> Option<Self> {
191        record::Span::slice(self, start..end)
192    }
193}
194
195fn is_digits<'a, S: Source<'a>>(value: S) -> bool {
196    value.span().iter().all(u8::is_ascii_digit)
197}
198
199impl<'a, S: Source<'a>> Parts<S> {
200    fn parse(value: S) -> Result<Self, Error> {
201        let len = value.len();
202        if len == 0 {
203            return Err(Error::Empty);
204        }
205        if value.get(0) == Some(b'[') {
206            let Some(close) = value.span().iter().position(|byte| *byte == b']') else {
207                return Err(Error::InvalidHost);
208            };
209            let Some(literal) = value.sub(1, close) else {
210                return Err(Error::InvalidHost);
211            };
212            let literal = literal.span();
213            if close == 1
214                || (!is_future_span(literal) && parser::Parser::new(literal).v6().is_none())
215            {
216                return Err(Error::InvalidHost);
217            }
218            let Some(suffix) = value.sub(close + 1, len) else {
219                return Err(Error::InvalidHost);
220            };
221            let port = if suffix.len() == 0 {
222                None
223            } else {
224                if suffix.get(0) != Some(b':') {
225                    return Err(Error::InvalidPort);
226                }
227                let Some(port) = suffix.sub(1, suffix.len()) else {
228                    return Err(Error::InvalidPort);
229                };
230                if !is_digits(port) {
231                    return Err(Error::InvalidPort);
232                }
233                Some(port)
234            };
235            let Some(host) = value.sub(0, close + 1) else {
236                return Err(Error::InvalidHost);
237            };
238            return Ok(Self { host, port });
239        }
240        let span = value.span();
241        let mut bytes = span.iter();
242        let colon = bytes.position(|byte| *byte == b':');
243        if bytes.any(|byte| *byte == b':') {
244            return Err(Error::InvalidHost);
245        }
246        let host_end = colon.unwrap_or(len);
247        let Some(host) = value.sub(0, host_end) else {
248            return Err(Error::InvalidHost);
249        };
250        if !is_reg_span(host.span()) {
251            return Err(Error::InvalidHost);
252        }
253        let port = match colon {
254            Some(index) => {
255                let Some(port) = value.sub(index + 1, len) else {
256                    return Err(Error::InvalidPort);
257                };
258                if !is_digits(port) {
259                    return Err(Error::InvalidPort);
260                }
261                Some(port)
262            }
263            None => None,
264        };
265        Ok(Self { host, port })
266    }
267}
268
269fn is_port_span_equivalent(
270    left: Option<record::Span<'_>>,
271    right: Option<record::Span<'_>>,
272    scheme: Scheme,
273) -> bool {
274    normalized_port_span(left, scheme) == normalized_port_span(right, scheme)
275}
276
277fn normalized_port_span(
278    port: Option<record::Span<'_>>,
279    scheme: Scheme,
280) -> Option<record::Span<'_>> {
281    let port = port.filter(|port| !port.is_empty())?;
282    let first = port.iter().position(|byte| *byte != b'0');
283    let start = match first {
284        Some(index) => index,
285        None => port.len() - 1,
286    };
287    let port = port.slice(start..port.len())?;
288    if (scheme == Scheme::Http && port.is_equal(b"80"))
289        || (scheme == Scheme::Https && port.is_equal(b"443"))
290    {
291        None
292    } else {
293        Some(port)
294    }
295}
296
297fn is_reg_span(value: record::Span<'_>) -> bool {
298    if value.is_empty() {
299        return false;
300    }
301    let mut bytes = value.iter().copied();
302    while let Some(byte) = bytes.next() {
303        if byte == b'%' {
304            if bytes.next().is_none_or(|byte| !byte.is_ascii_hexdigit())
305                || bytes.next().is_none_or(|byte| !byte.is_ascii_hexdigit())
306            {
307                return false;
308            }
309        } else if !is_reg_byte(byte) {
310            return false;
311        }
312    }
313    true
314}
315
316fn is_reg_byte(byte: u8) -> bool {
317    byte.is_ascii_alphanumeric()
318        || matches!(
319            byte,
320            b'-' | b'.'
321                | b'_'
322                | b'~'
323                | b'!'
324                | b'$'
325                | b'&'
326                | b'\''
327                | b'('
328                | b')'
329                | b'*'
330                | b'+'
331                | b','
332                | b';'
333                | b'='
334        )
335}
336
337fn is_future_byte(byte: u8) -> bool {
338    is_reg_byte(byte) || byte == b':'
339}
340
341fn is_future_span(value: record::Span<'_>) -> bool {
342    if !matches!(value.get(0), Some(b'v' | b'V')) {
343        return false;
344    }
345    let Some(dot) = value.iter().position(|byte| *byte == b'.') else {
346        return false;
347    };
348    dot > 1
349        && (1..dot).all(|index| {
350            value
351                .get(index)
352                .is_some_and(|byte| byte.is_ascii_hexdigit())
353        })
354        && dot + 1 < value.len()
355        && (dot + 1..value.len()).all(|index| value.get(index).is_some_and(is_future_byte))
356}