Skip to main content

acdc_parser/
safe_mode.rs

1use std::str::FromStr;
2
3/// Controls include access and exposure of local paths during parsing.
4///
5/// Defaults to [`Self::Secure`]. Applications must select a lower mode to load
6/// include targets. The CLI selects its own defaults for each command.
7///
8/// Fixed resource limits apply in every mode. Output converters are responsible
9/// for restrictions on stylesheets, scripts, and other rendered resources.
10#[derive(Debug, Clone, Default, PartialOrd, PartialEq, Eq, Copy)]
11pub enum SafeMode {
12    /// Allows local includes without a base-directory boundary.
13    ///
14    /// HTTP(S) includes require caller-supplied `allow-uri-read`. The built-in
15    /// transport also requires the `network` feature; custom providers supply
16    /// their own transport. Include depth and selected text size limits still apply.
17    Unsafe = 0,
18
19    /// Keeps local include paths beneath the effective include base directory.
20    ///
21    /// The base defaults to the entry file's parent, or the current directory for
22    /// string and reader input. [`crate::OptionsBuilder::with_base_dir`] overrides it.
23    /// For `/workspace/docs/main.adoc`, `../shared.adoc` becomes
24    /// `/workspace/docs/shared.adoc`, while `/tmp/shared.adoc` becomes
25    /// `/workspace/docs/tmp/shared.adoc`; both transformations emit a warning. This
26    /// check does not resolve symlinks, so `/workspace/docs/linked.adoc` may point
27    /// outside the directory. HTTP(S) includes require caller permission as in Unsafe mode.
28    Safe,
29
30    /// Applies Safe mode's include boundary and hides local directory and home paths.
31    ///
32    /// `docfile` exposes the entry filename instead of its full path. HTTP(S)
33    /// includes require caller permission as in Unsafe mode.
34    Server,
35
36    /// Disables local and remote include reads, and hides paths as in Server mode.
37    ///
38    /// Include directives become link fallbacks with source-recovery warnings.
39    /// [`crate::parse_file`] can still read the entry file selected by the caller.
40    #[default]
41    Secure,
42}
43
44impl FromStr for SafeMode {
45    type Err = String;
46
47    fn from_str(s: &str) -> Result<Self, Self::Err> {
48        match s.to_lowercase().as_str() {
49            "unsafe" => Ok(Self::Unsafe),
50            "safe" => Ok(Self::Safe),
51            "server" => Ok(Self::Server),
52            "secure" => Ok(Self::Secure),
53            _ => Err(format!(
54                "invalid safe mode: '{s}', expected: unsafe, safe, server, secure"
55            )),
56        }
57    }
58}
59
60impl SafeMode {
61    #[must_use]
62    pub const fn level(self) -> u8 {
63        match self {
64            Self::Unsafe => 0,
65            Self::Safe => 1,
66            Self::Server => 10,
67            Self::Secure => 20,
68        }
69    }
70
71    #[must_use]
72    pub const fn name(self) -> &'static str {
73        match self {
74            Self::Unsafe => "unsafe",
75            Self::Safe => "safe",
76            Self::Server => "server",
77            Self::Secure => "secure",
78        }
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn test_from_str() -> Result<(), String> {
88        assert_eq!(SafeMode::from_str("unsafe")?, SafeMode::Unsafe);
89        assert_eq!(SafeMode::from_str("UNSAFE")?, SafeMode::Unsafe);
90        assert_eq!(SafeMode::from_str("safe")?, SafeMode::Safe);
91        assert_eq!(SafeMode::from_str("server")?, SafeMode::Server);
92        assert_eq!(SafeMode::from_str("secure")?, SafeMode::Secure);
93        assert!(SafeMode::from_str("invalid").is_err());
94        Ok(())
95    }
96
97    #[test]
98    fn test_ordering() {
99        assert!(SafeMode::Unsafe < SafeMode::Safe);
100        assert!(SafeMode::Safe < SafeMode::Server);
101        assert!(SafeMode::Server < SafeMode::Secure);
102    }
103}