Skip to main content

eggserve_core/
policy.rs

1//! Security policy types for filesystem access control.
2//!
3//! All policy types default to the most restrictive setting. Callers must
4//! explicitly opt in to less restrictive behaviors.
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7#[allow(dead_code)]
8pub(crate) enum PolicyMode {
9    Strict,
10    Compat,
11}
12
13/// Controls whether directory listings are generated for directory requests
14/// that lack an `index.html`.
15///
16/// Default: `Disabled`. Directories without an index file return 403.
17#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
18pub enum DirectoryListingPolicy {
19    #[default]
20    Disabled,
21    Enabled,
22}
23
24/// Controls whether symbolic links are followed during path resolution.
25///
26/// Default: `Denied`. Symlinks are refused at the filesystem layer using
27/// descriptor-relative traversal (`openat` with `O_NOFOLLOW` on Unix).
28#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
29pub enum SymlinkPolicy {
30    #[default]
31    Denied,
32    Follow,
33}
34
35/// Controls whether dotfiles (paths containing a component starting with `.`)
36/// are served.
37///
38/// Default: `Denied`. Dotfiles return 403.
39#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
40pub enum DotfilePolicy {
41    #[default]
42    Denied,
43    Serve,
44}
45
46/// Composite security policy for static file serving.
47///
48/// Combines directory listing, symlink, and dotfile policies into a single
49/// configuration. [`StaticPolicy::safe_default()`] denies all optional
50/// behaviors; callers must explicitly opt in.
51///
52/// # Examples
53///
54/// ```
55/// use eggserve_core::policy::{StaticPolicy, DirectoryListingPolicy};
56///
57/// let mut policy = StaticPolicy::safe_default();
58/// policy.directory_listing = DirectoryListingPolicy::Enabled;
59/// ```
60#[derive(Debug, Clone)]
61#[must_use]
62pub struct StaticPolicy {
63    pub directory_listing: DirectoryListingPolicy,
64    pub symlinks: SymlinkPolicy,
65    pub dotfiles: DotfilePolicy,
66}
67
68impl Default for StaticPolicy {
69    fn default() -> Self {
70        Self::safe_default()
71    }
72}
73
74impl StaticPolicy {
75    pub fn safe_default() -> Self {
76        Self {
77            directory_listing: DirectoryListingPolicy::Disabled,
78            symlinks: SymlinkPolicy::Denied,
79            dotfiles: DotfilePolicy::Denied,
80        }
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn safe_default_disables_directory_listing() {
90        let policy = StaticPolicy::safe_default();
91        assert_eq!(policy.directory_listing, DirectoryListingPolicy::Disabled);
92    }
93
94    #[test]
95    fn safe_default_denies_symlinks() {
96        let policy = StaticPolicy::safe_default();
97        assert_eq!(policy.symlinks, SymlinkPolicy::Denied);
98    }
99
100    #[test]
101    fn safe_default_denies_dotfiles() {
102        let policy = StaticPolicy::safe_default();
103        assert_eq!(policy.dotfiles, DotfilePolicy::Denied);
104    }
105}