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