Skip to main content

pharos_runtime_core/
lib.rs

1#![forbid(unsafe_code)]
2
3/// Provides the `application_ir` module.
4///
5/// # Examples
6///
7/// ```rust,ignore
8/// use pharos_runtime_core::application_ir;
9/// ```
10///
11/// # Related
12///
13/// - [`crate`] for sibling modules in the same crate.
14pub mod application_ir;
15/// Provides framework builtin response helpers.
16pub mod builtin;
17/// Provides the `config` module.
18///
19/// # Examples
20///
21/// ```rust,ignore
22/// use pharos_runtime_core::config;
23/// ```
24///
25/// # Related
26///
27/// - [`crate`] for sibling modules in the same crate.
28pub mod config;
29/// Provides the `connection` module.
30///
31/// # Examples
32///
33/// ```rust,ignore
34/// use pharos_runtime_core::connection;
35/// ```
36///
37/// # Related
38///
39/// - [`crate`] for sibling modules in the same crate.
40pub mod connection;
41/// Provides the `error` module.
42///
43/// # Examples
44///
45/// ```rust,ignore
46/// use pharos_runtime_core::error;
47/// ```
48///
49/// # Related
50///
51/// - [`crate`] for sibling modules in the same crate.
52pub mod error;
53/// Provides the `fs` module.
54///
55/// # Examples
56///
57/// ```rust,ignore
58/// use pharos_runtime_core::fs;
59/// ```
60///
61/// # Related
62///
63/// - [`crate`] for sibling modules in the same crate.
64pub mod fs;
65/// Provides the `http` module.
66///
67/// # Examples
68///
69/// ```rust,ignore
70/// use pharos_runtime_core::http;
71/// ```
72///
73/// # Related
74///
75/// - [`crate`] for sibling modules in the same crate.
76pub mod http;
77/// Provides the `listener` module.
78///
79/// # Examples
80///
81/// ```rust,ignore
82/// use pharos_runtime_core::listener;
83/// ```
84///
85/// # Related
86///
87/// - [`crate`] for sibling modules in the same crate.
88pub mod listener;
89/// Provides runtime readiness helpers.
90pub mod readiness;
91/// Provides app-neutral render request data helpers.
92pub mod render;
93/// Provides runtime request dispatch helpers.
94pub mod request;
95/// Provides runtime route lookup helpers.
96pub mod route;
97/// Provides runtime stack records.
98pub mod stack;
99
100/// Provides the `support` module.
101///
102/// # Examples
103///
104/// ```rust,ignore
105/// use pharos_runtime_core::support;
106/// ```
107///
108/// # Related
109///
110/// - [`crate`] for sibling modules in the same crate.
111pub mod support;
112/// Provides runtime timestamp formatting helpers.
113///
114/// # Examples
115///
116/// ```rust,ignore
117/// use pharos_runtime_core::time;
118/// ```
119///
120/// # Related
121///
122/// - [`crate`] for sibling modules in the same crate.
123pub mod time;
124/// Provides the `worker_pool` module.
125///
126/// # Examples
127///
128/// ```rust,ignore
129/// use pharos_runtime_core::worker_pool;
130/// ```
131///
132/// # Related
133///
134/// - [`crate`] for sibling modules in the same crate.
135pub mod worker_pool;
136/// Provides the `yaml` module.
137///
138/// # Examples
139///
140/// ```rust,ignore
141/// use pharos_runtime_core::yaml;
142/// ```
143///
144/// # Related
145///
146/// - [`crate`] for sibling modules in the same crate.
147pub mod yaml;
148
149use std::path::{Component, Path, PathBuf};
150
151/// Returns the longest matching mount index for an absolute request path.
152///
153/// # Parameters
154///
155/// - `path`: Input consumed by [`crate::best_mount_index`].
156/// - `mounts`: Input consumed by [`crate::best_mount_index`].
157/// - `'a str`: Input consumed by [`crate::best_mount_index`].
158///
159/// # Returns
160///
161/// Returns [`Some`] when [`crate::best_mount_index`] can produce a value and [`None`] otherwise.
162///
163/// # Performance
164///
165/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::best_mount_index`].
166///
167/// # Examples
168///
169/// ```rust,ignore
170/// let _result = pharos_runtime_core::best_mount_index(std::path::Path::new("fixture.json"), todo!(), todo!());
171/// ```
172///
173/// # Related
174///
175/// - [`crate`] for adjacent helpers that share the same runtime surface.
176pub fn best_mount_index<'a, I>(path: &str, mounts: I) -> Option<usize>
177where
178    I: IntoIterator<Item = (usize, &'a str)>,
179{
180    let mut best = None;
181    let mut best_len = 0usize;
182    for (index, mount) in mounts {
183        let mount_len = mount.len();
184        if mount_len == 0 {
185            if best.is_none() {
186                best = Some(index);
187            }
188            continue;
189        }
190        let matches_mount = path == mount
191            || (path.starts_with(mount)
192                && matches!(
193                    path.as_bytes().get(mount_len),
194                    Some(b'/') | Some(b'?') | None
195                ));
196        if matches_mount && mount_len > best_len {
197            best = Some(index);
198            best_len = mount_len;
199        }
200    }
201    best
202}
203
204/// Removes one app mount prefix while preserving the query string.
205///
206/// # Parameters
207///
208/// - `mount`: Input consumed by [`crate::relative_target`].
209/// - `target`: Input consumed by [`crate::relative_target`].
210///
211/// # Returns
212///
213/// Returns the computed value produced by [`crate::relative_target`].
214///
215/// # Examples
216///
217/// ```rust,ignore
218/// let _result = pharos_runtime_core::relative_target(None, "/");
219/// ```
220///
221/// # Related
222///
223/// - [`crate`] for adjacent helpers that share the same runtime surface.
224pub fn relative_target(mount: Option<&str>, target: &str) -> String {
225    let mount = mount
226        .filter(|value| !value.is_empty() && *value != "/")
227        .map(|value| value.trim_end_matches('/'));
228    let Some(mount) = mount else {
229        return target.to_owned();
230    };
231    let (path, query) = target.split_once('?').unwrap_or((target, ""));
232    let relative_path = if path == mount {
233        "/".to_owned()
234    } else if let Some(suffix) = path.strip_prefix(mount) {
235        if suffix.starts_with('/') {
236            suffix.to_owned()
237        } else {
238            path.to_owned()
239        }
240    } else {
241        path.to_owned()
242    };
243    if query.is_empty() {
244        relative_path
245    } else {
246        format!("{relative_path}?{query}")
247    }
248}
249
250/// Normalizes an absolute request path without permitting parent traversal.
251///
252/// # Parameters
253///
254/// - `target`: Input consumed by [`crate::normalize_request_path`].
255/// - `max_bytes`: Input consumed by [`crate::normalize_request_path`].
256///
257/// # Returns
258///
259/// Returns [`Some`] when [`crate::normalize_request_path`] can produce a value and [`None`] otherwise.
260///
261/// # Performance
262///
263/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::normalize_request_path`].
264///
265/// # Examples
266///
267/// ```rust,ignore
268/// let _result = pharos_runtime_core::normalize_request_path("/", 1024);
269/// ```
270///
271/// # Related
272///
273/// - [`crate`] for adjacent helpers that share the same runtime surface.
274pub fn normalize_request_path(target: &str, max_bytes: usize) -> Option<String> {
275    if target.is_empty() || target.len() > max_bytes {
276        return None;
277    }
278    let raw_path = target.split(['?', '#']).next().unwrap_or_default();
279    if !raw_path.starts_with('/') || raw_path.contains('\\') {
280        return None;
281    }
282    let mut normalized = String::from("/");
283    let mut first = true;
284    for component in Path::new(raw_path).components() {
285        match component {
286            Component::RootDir | Component::CurDir => {}
287            Component::ParentDir | Component::Prefix(_) => return None,
288            Component::Normal(segment) => {
289                let segment = segment.to_str()?;
290                if segment.is_empty() {
291                    continue;
292                }
293                if !first {
294                    normalized.push('/');
295                }
296                normalized.push_str(segment);
297                first = false;
298            }
299        }
300    }
301    Some(normalized)
302}
303
304/// Resolves a relative artifact path and proves it remains under the artifact root.
305///
306/// # Parameters
307///
308/// - `root`: Input consumed by [`crate::safe_existing_artifact_path`].
309/// - `relative`: Input consumed by [`crate::safe_existing_artifact_path`].
310///
311/// # Returns
312///
313/// Returns [`Some`] when [`crate::safe_existing_artifact_path`] can produce a value and [`None`] otherwise.
314///
315/// # Examples
316///
317/// ```rust,ignore
318/// let _result = pharos_runtime_core::safe_existing_artifact_path(std::path::Path::new("."), todo!());
319/// ```
320///
321/// # Related
322///
323/// - [`crate`] for adjacent helpers that share the same runtime surface.
324pub fn safe_existing_artifact_path(root: &Path, relative: &str) -> Option<PathBuf> {
325    if relative.is_empty() || Path::new(relative).is_absolute() || relative.contains('\\') {
326        return None;
327    }
328    let canonical_root = std::fs::canonicalize(root).ok()?;
329    let canonical_candidate = std::fs::canonicalize(root.join(relative)).ok()?;
330    canonical_candidate
331        .starts_with(canonical_root)
332        .then_some(canonical_candidate)
333}
334
335#[cfg(test)]
336mod tests {
337    use super::{best_mount_index, normalize_request_path, relative_target};
338
339    #[test]
340    /// Performs prefers longest matching mount.
341    fn prefers_longest_matching_mount() {
342        let mounts = [(0, ""), (1, "/docs"), (2, "/docs/reference")];
343        assert_eq!(best_mount_index("/docs/reference/page", mounts), Some(2));
344    }
345
346    #[test]
347    /// Performs preserves query when trimming mount.
348    fn preserves_query_when_trimming_mount() {
349        assert_eq!(
350            relative_target(Some("/ucal"), "/ucal/accounts/login/?next=/"),
351            "/accounts/login/?next=/"
352        );
353    }
354
355    #[test]
356    /// Performs rejects parent path components.
357    fn rejects_parent_path_components() {
358        assert_eq!(normalize_request_path("/static/../secret", 8192), None);
359    }
360}