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}