Skip to main content

sark_http/uri/target/relative/
mod.rs

1use std::iter;
2
3use o3::alloc::array;
4
5use crate::uri::{
6    path,
7    target::{self, Error, Origin, Slash, Target},
8};
9
10mod raw;
11
12/// An admitted relative or origin-form path and optional query.
13#[derive(Clone, Copy)]
14pub struct Relative<'a> {
15    value: &'a str,
16    path_end: u32,
17}
18
19impl<'a> Relative<'a> {
20    pub(super) fn from_target(target: &'a Target) -> Self {
21        Self {
22            value: target.path_query(),
23            path_end: target.path_end - target.authority_end,
24        }
25    }
26
27    pub fn parse(value: &'a str) -> Result<Self, Error> {
28        let plan = path::Scan::new(value.as_bytes())
29            .complete()
30            .map_err(Error::Origin)?;
31        Ok(Self {
32            value,
33            path_end: target::narrow(plan.path_end())?,
34        })
35    }
36
37    /// Resolves only admitted components; normalization cannot introduce new bytes
38    /// other than path separators, so the original grammar proof remains valid.
39    /// Materialization precedes offset narrowing.
40    pub fn resolve(self, base: &Target) -> Result<Target, Error> {
41        if self.value.is_empty() {
42            return base.try_clone().map_err(Error::Allocation);
43        }
44        if self.value.starts_with('/') && !self.has_dots() {
45            return base.with_origin(Origin {
46                value: self.value,
47                path_end: self.path_end,
48                slash: Slash::Present,
49            });
50        }
51        self.materialize(base)
52    }
53
54    pub(super) fn materialize(self, base: &Target) -> Result<Target, Error> {
55        // SAFETY: complete admission retains the ASCII path boundary in these immutable bytes.
56        let (path, query) = unsafe {
57            (
58                target::raw::slice(self.value, 0..self.path_end as usize),
59                target::raw::slice(self.value, self.path_end as usize..self.value.len()),
60            )
61        };
62        let (value, path_end) = if path.is_empty() {
63            let capacity = (base.path_end as usize)
64                .checked_add(query.len())
65                .ok_or(Error::Length)?;
66            let mut value = target::buffer(capacity)?;
67            // SAFETY: the base owns this admitted ASCII path prefix without mutation.
68            value.push_str(unsafe { target::raw::slice(&base.value, 0..base.path_end as usize) });
69            value.push_str(query);
70            (value, base.path_end)
71        } else {
72            let (prefix, path) = match path.strip_prefix('/') {
73                Some(path) => ("", path),
74                None => {
75                    let base = base.path();
76                    let end = base.rfind('/').unwrap_or(0) + 1;
77                    // SAFETY: the admitted path starts with '/' and rfind locates its final separator.
78                    (unsafe { target::raw::slice(base, 1..end) }, path)
79                }
80            };
81            let segments = segments(prefix, path);
82            let path_len = segments.clone().try_fold(0usize, |len, part| {
83                len.checked_add(part.len() + 1).ok_or(Error::Length)
84            })?;
85            let path_end = (base.authority_end as usize)
86                .checked_add(path_len)
87                .ok_or(Error::Length)?;
88            let capacity = path_end.checked_add(query.len()).ok_or(Error::Length)?;
89            let buffer = array::Plan::try_new(capacity).map_err(|_| Error::Length)?;
90            // SAFETY: the clone measured this same deterministic iterator over
91            // immutable segments, including one preceding slash per segment.
92            let value = unsafe {
93                raw::normalize(
94                    buffer,
95                    target::raw::slice(&base.value, 0..base.authority_end as usize),
96                    path_len,
97                    segments,
98                    query,
99                )
100            }
101            .map_err(Error::Allocation)?;
102            target::narrow(path_len)?;
103            let path_end = target::narrow(path_end)?;
104            (value, path_end)
105        };
106        Ok(Target {
107            // SAFETY: both branches initialize every byte of their exact-capacity allocation.
108            value: unsafe { target::raw::boxed(value) },
109            scheme_end: base.scheme_end,
110            host_end: base.host_end,
111            authority_end: base.authority_end,
112            path_end,
113        })
114    }
115
116    pub(super) fn has_dots(self) -> bool {
117        // SAFETY: admission retains this ASCII path prefix on the unchanged borrowed bytes.
118        unsafe { target::raw::slice(self.value, 0..self.path_end as usize) }
119            .split('/')
120            .any(|part| matches!(part, "." | ".."))
121    }
122}
123
124/// Yields normalized segments from the end without storing or joining them.
125/// `prefix` excludes the root slash and ends at a directory separator.
126fn segments<'a>(prefix: &'a str, path: &'a str) -> impl Iterator<Item = &'a str> + Clone {
127    let trailing = matches!(path.rsplit('/').next(), Some("." | ".."));
128    let mut parents = 0usize;
129    iter::once("").take(usize::from(trailing)).chain(
130        path.rsplit('/')
131            .chain(prefix.rsplit_terminator('/'))
132            .filter(move |part| match *part {
133                "." => false,
134                ".." => {
135                    parents += 1;
136                    false
137                }
138                _ if parents != 0 => {
139                    parents -= 1;
140                    false
141                }
142                _ => true,
143            }),
144    )
145}