Skip to main content

sark_http/uri/target/
mod.rs

1//! Lossless absolute-URI and client-target proofs.
2
3use std::{num, str};
4
5use o3::alloc::{self, StrExt as _, array};
6
7use crate::uri::{self, path};
8
9mod plan;
10pub use plan::Plan;
11mod raw;
12mod relative;
13pub use relative::Relative;
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub enum Error {
17    Colon,
18    Scheme(uri::Error),
19    Separator { colon: usize, len: usize },
20    UserInfo,
21    Authority(uri::Error),
22    Start { byte: Option<u8> },
23    Origin(path::Error),
24    Length,
25    Index { value: usize },
26    Allocation(alloc::Exhausted),
27}
28
29#[derive(Clone, Copy, PartialEq, Eq)]
30pub struct Absolute<'a> {
31    scheme: &'a str,
32    parsed: uri::Authority<'a>,
33    origin: Origin<'a>,
34}
35
36impl<'a> Absolute<'a> {
37    pub fn parse(value: &'a str) -> Result<Self, Error> {
38        let bytes = value.as_bytes();
39        let colon = bytes
40            .iter()
41            .position(|byte| *byte == b':')
42            .ok_or(Error::Colon)?;
43        // SAFETY: the located ASCII ':' ends this UTF-8 prefix inside the same input.
44        let scheme = unsafe { raw::slice(value, 0..colon) };
45        uri::Scheme::validate(scheme.as_bytes()).map_err(Error::Scheme)?;
46        let start = colon.checked_add(3).ok_or(Error::Length)?;
47        if bytes.get(colon..start) != Some(b"://") {
48            return Err(Error::Separator {
49                colon,
50                len: bytes.len(),
51            });
52        }
53        let rest = bytes.get(start..).ok_or(Error::Length)?;
54        let len = match rest
55            .iter()
56            .position(|byte| matches!(*byte, b'/' | b'?' | b'#'))
57        {
58            Some(len) => len,
59            None => rest.len(),
60        };
61        let end = start.checked_add(len).ok_or(Error::Length)?;
62        let raw = value.get(start..end).ok_or(Error::Length)?;
63        let parsed = authority(raw)?;
64        let origin = Origin::from_uri(value.get(end..).ok_or(Error::Length)?)?;
65        Ok(Self {
66            scheme,
67            parsed,
68            origin,
69        })
70    }
71
72    pub const fn scheme(self) -> &'a str {
73        self.scheme
74    }
75
76    pub const fn authority(self) -> uri::Authority<'a> {
77        self.parsed
78    }
79
80    pub const fn origin(self) -> Origin<'a> {
81        self.origin
82    }
83}
84
85#[derive(Clone, Copy, PartialEq, Eq)]
86pub struct Origin<'a> {
87    value: &'a str,
88    path_end: u32,
89    slash: Slash,
90}
91
92#[derive(Clone, Copy, PartialEq, Eq)]
93#[repr(u8)]
94enum Slash {
95    Present,
96    Insert,
97}
98
99impl<'a> Origin<'a> {
100    pub const ROOT: Self = Self {
101        value: "/",
102        path_end: 1,
103        slash: Slash::Present,
104    };
105
106    pub fn parse(value: &'a str) -> Result<Self, Error> {
107        if !value.starts_with('/') {
108            return Err(Error::Start {
109                byte: value.as_bytes().first().copied(),
110            });
111        }
112        Self::validated(value, Slash::Present)
113    }
114
115    fn from_uri(value: &'a str) -> Result<Self, Error> {
116        match value.as_bytes().first() {
117            Some(b'/') => Self::validated(value, Slash::Present),
118            Some(b'?') => Self::validated(value, Slash::Insert),
119            Some(byte) => Err(Error::Start { byte: Some(*byte) }),
120            None => Self::validated(value, Slash::Insert),
121        }
122    }
123
124    fn validated(value: &'a str, slash: Slash) -> Result<Self, Error> {
125        let plan = path::Scan::new(value.as_bytes())
126            .complete()
127            .map_err(Error::Origin)?;
128        Ok(Self {
129            value,
130            path_end: narrow(plan.path_end())?,
131            slash,
132        })
133    }
134
135    fn len(self) -> Result<usize, Error> {
136        let prefix = match self.slash {
137            Slash::Present => 0,
138            Slash::Insert => 1,
139        };
140        self.value.len().checked_add(prefix).ok_or(Error::Length)
141    }
142
143    fn append_to(self, output: &mut String) {
144        match self.slash {
145            Slash::Present => {}
146            Slash::Insert => output.push('/'),
147        }
148        output.push_str(self.value);
149    }
150
151    fn path_end(self) -> usize {
152        self.path_end as usize + usize::from(self.slash == Slash::Insert)
153    }
154}
155
156/// An owned URI target with one admitted authority and origin form.
157///
158/// Component offsets retain the parser's bounds and grammar proofs. [`Self::try_clone`]
159/// copies the owned bytes; targets never share reference-counted storage.
160#[derive(Debug)]
161pub struct Target {
162    value: Box<str>,
163    scheme_end: Option<num::NonZero<u32>>,
164    host_end: u32,
165    authority_end: u32,
166    path_end: u32,
167}
168
169impl Target {
170    pub fn parse(value: &str) -> Result<Self, Error> {
171        let parsed = Absolute::parse(value)?;
172        let start = parsed.scheme().len() + 3;
173        let end = value.len() - parsed.origin().value.len();
174        // SAFETY: parsing retained these authority boundaries in this exact immutable input.
175        let raw = unsafe { raw::slice(value, start..end) };
176        pack(
177            Some(parsed.scheme()),
178            raw,
179            parsed.authority(),
180            parsed.origin(),
181        )
182    }
183
184    pub fn new(raw: &str, origin: Origin<'_>) -> Result<Self, Error> {
185        let parsed = authority(raw)?;
186        pack(None, raw, parsed, origin)
187    }
188
189    pub fn from_host(
190        host: &str,
191        port: Option<num::NonZero<u16>>,
192        origin: Origin<'_>,
193    ) -> Result<Self, Error> {
194        let brackets = host.contains(':') && !host.starts_with('[');
195        let host_end = if brackets {
196            if !uri::Authority::is_ip(host.as_bytes()) {
197                return Err(Error::Authority(uri::Error::InvalidHost));
198            }
199            host.len().checked_add(2).ok_or(Error::Length)?
200        } else {
201            if host.is_empty() && port.is_some() {
202                return Err(Error::Authority(uri::Error::InvalidHost));
203            }
204            let parsed = uri::Authority::parse(host.as_bytes()).map_err(Error::Authority)?;
205            if port.is_some() && parsed.port().is_some() {
206                return Err(Error::Authority(uri::Error::InvalidPort));
207            }
208            parsed.host().len()
209        };
210        let mut digits = [0; 20];
211        let port = port.map(|port| raw::decimal(port, &mut digits));
212        let port_len = port.map_or(0, |port| port.len() + 1);
213        let end = host
214            .len()
215            .checked_add(usize::from(brackets) * 2)
216            .and_then(|len| len.checked_add(port_len))
217            .ok_or(Error::Length)?;
218        let capacity = end.checked_add(origin.len()?).ok_or(Error::Length)?;
219        let path_end = end.checked_add(origin.path_end()).ok_or(Error::Length)?;
220        let bounds = plan::Bounds::new(0, host_end, end, path_end)?;
221        let mut value = buffer(capacity)?;
222        if brackets {
223            value.push('[');
224        }
225        value.push_str(host);
226        if brackets {
227            value.push(']');
228        }
229        if let Some(port) = port {
230            value.push(':');
231            value.push_str(port);
232        }
233        origin.append_to(&mut value);
234        // SAFETY: every measured host, port, and origin byte filled the exact capacity.
235        Ok(bounds.build(unsafe { raw::boxed(value) }))
236    }
237
238    pub fn scheme_str(&self) -> Option<&str> {
239        self.scheme_end.map(|end| {
240            // SAFETY: admission retains the ASCII scheme prefix in this immutable owner.
241            unsafe { raw::slice(&self.value, 0..end.get() as usize) }
242        })
243    }
244
245    /// Copies the admitted bytes and offsets without parsing again.
246    pub fn try_clone(&self) -> Result<Self, alloc::Exhausted> {
247        let value = self.value.try_to_owned()?;
248        Ok(Self {
249            // SAFETY: StrExt copies this exact UTF-8 allocation with length equal to capacity.
250            value: unsafe { raw::boxed(value) },
251            scheme_end: self.scheme_end,
252            host_end: self.host_end,
253            authority_end: self.authority_end,
254            path_end: self.path_end,
255        })
256    }
257
258    pub fn authority(&self) -> &str {
259        // SAFETY: construction retains these authority boundaries in this immutable owner.
260        unsafe { raw::slice(&self.value, self.start()..self.authority_end as usize) }
261    }
262
263    /// The host spelling in the authority, including IPv6 brackets.
264    pub fn host_raw(&self) -> &str {
265        // SAFETY: construction retains this authority host range on ASCII boundaries.
266        unsafe { raw::slice(&self.value, self.start()..self.host_end as usize) }
267    }
268
269    pub fn host(&self) -> &str {
270        let host = self.host_raw();
271        host.strip_prefix('[')
272            .and_then(|host| host.strip_suffix(']'))
273            .unwrap_or(host)
274    }
275
276    pub fn path(&self) -> &str {
277        let range = self.authority_end as usize..self.path_end as usize;
278        // SAFETY: admission retains the complete ASCII path between these boundaries.
279        unsafe { raw::slice(&self.value, range) }
280    }
281
282    pub fn path_query(&self) -> &str {
283        // SAFETY: the admitted ASCII authority ends at this immutable owner's origin.
284        unsafe { raw::slice(&self.value, self.authority_end as usize..self.value.len()) }
285    }
286
287    pub fn query(&self) -> Option<&str> {
288        let end = self.path_end as usize;
289        (end != self.value.len()).then(|| {
290            // SAFETY: an admitted nonterminal path boundary contains its ASCII query separator.
291            unsafe { raw::slice(&self.value, end + 1..self.value.len()) }
292        })
293    }
294
295    pub fn retarget(self, raw: &str) -> Result<Self, Error> {
296        let parsed = authority(raw)?;
297        pack(self.scheme_str(), raw, parsed, Origin::from(&self))
298    }
299
300    pub fn with_origin(&self, origin: Origin<'_>) -> Result<Self, Error> {
301        pack(self.scheme_str(), self.authority(), self.parts(), origin)
302    }
303
304    /// Removes dot segments while retaining the admitted component syntax.
305    pub fn normalize(self) -> Result<Self, Error> {
306        let relative = Relative::from_target(&self);
307        if !relative.has_dots() {
308            return Ok(self);
309        }
310        relative.materialize(&self)
311    }
312
313    fn start(&self) -> usize {
314        match self.scheme_end {
315            Some(end) => end.get() as usize + 3,
316            None => 0,
317        }
318    }
319
320    /// Borrows the authority components proved when this target was admitted.
321    pub fn parts(&self) -> uri::Authority<'_> {
322        let host = self.host_raw().as_bytes();
323        let port = (self.host_end != self.authority_end).then(|| {
324            let range = self.host_end as usize + 1..self.authority_end as usize;
325            // SAFETY: admission places ':' after the host and proves this ASCII port range.
326            unsafe { raw::slice(&self.value, range) }.as_bytes()
327        });
328        uri::Authority::new(host, port)
329    }
330}
331
332impl<'a> From<&'a Target> for Origin<'a> {
333    fn from(target: &'a Target) -> Self {
334        Self {
335            value: target.path_query(),
336            path_end: target.path_end - target.authority_end,
337            slash: Slash::Present,
338        }
339    }
340}
341
342impl AsRef<str> for Target {
343    fn as_ref(&self) -> &str {
344        &self.value
345    }
346}
347
348impl str::FromStr for Target {
349    type Err = Error;
350
351    fn from_str(value: &str) -> Result<Self, Self::Err> {
352        if !value.contains("://") {
353            return Self::new(value, Origin::ROOT);
354        }
355        Self::parse(value)
356    }
357}
358
359impl PartialEq for Target {
360    fn eq(&self, other: &Self) -> bool {
361        let scheme = match (self.scheme_str(), other.scheme_str()) {
362            (Some(left), Some(right)) => left.eq_ignore_ascii_case(right),
363            (None, None) => true,
364            _ => false,
365        };
366        scheme
367            && self.authority().eq_ignore_ascii_case(other.authority())
368            && self.path_query() == other.path_query()
369    }
370}
371
372impl Eq for Target {}
373
374fn authority(raw: &str) -> Result<uri::Authority<'_>, Error> {
375    if raw.as_bytes().contains(&b'@') {
376        return Err(Error::UserInfo);
377    }
378    uri::Authority::parse(raw.as_bytes()).map_err(Error::Authority)
379}
380
381fn pack(
382    scheme: Option<&str>,
383    raw: &str,
384    parsed: uri::Authority<'_>,
385    origin: Origin<'_>,
386) -> Result<Target, Error> {
387    let start = match scheme {
388        Some(scheme) => scheme.len().checked_add(3).ok_or(Error::Length)?,
389        None => 0,
390    };
391    let end = start.checked_add(raw.len()).ok_or(Error::Length)?;
392    let capacity = end.checked_add(origin.len()?).ok_or(Error::Length)?;
393    let host_end = start + parsed.host().len();
394    let path_end = end.checked_add(origin.path_end()).ok_or(Error::Length)?;
395    let bounds = plan::Bounds::new(scheme.map_or(0, str::len), host_end, end, path_end)?;
396    let mut value = buffer(capacity)?;
397    if let Some(scheme) = scheme {
398        value.push_str(scheme);
399        value.push_str("://");
400    }
401    value.push_str(raw);
402    origin.append_to(&mut value);
403    // SAFETY: the scheme, authority, and origin filled the exact measured capacity.
404    Ok(bounds.build(unsafe { raw::boxed(value) }))
405}
406
407fn narrow(value: usize) -> Result<u32, Error> {
408    if value > u32::MAX as usize {
409        return Err(Error::Index { value });
410    }
411    Ok(value as u32)
412}
413
414fn buffer(capacity: usize) -> Result<String, Error> {
415    array::Plan::try_new(capacity)
416        .map_err(|_| Error::Length)?
417        .try_string()
418        .map_err(Error::Allocation)
419}
420
421const _: () = assert!(core::mem::size_of::<Absolute<'_>>() <= 9 * core::mem::size_of::<usize>());
422const _: () = assert!(core::mem::size_of::<Slash>() == core::mem::size_of::<bool>());
423const _: () = assert!(core::mem::size_of::<Origin<'_>>() <= 3 * core::mem::size_of::<usize>());
424const _: () = assert!(core::mem::size_of::<Target>() <= 4 * core::mem::size_of::<usize>());