1pub const CAP_VARIABLES_READ: u32 = 0x01;
10pub const CAP_VARIABLES_WRITE: u32 = 0x02;
11pub const CAP_FILESYSTEM: u32 = 0x04;
12pub const CAP_IO: u32 = 0x08;
13pub const CAP_HOOK_PRE_EXEC: u32 = 0x10;
14pub const CAP_HOOK_POST_EXEC: u32 = 0x20;
15pub const CAP_HOOK_ON_CD: u32 = 0x40;
16pub const CAP_HOOK_PRE_PROMPT: u32 = 0x80;
17pub const CAP_FILES_READ: u32 = 0x100;
18pub const CAP_FILES_WRITE: u32 = 0x200;
19pub const CAP_COMMANDS_EXEC: u32 = 0x400;
20
21pub const CAP_ALL: u32 = CAP_VARIABLES_READ
22 | CAP_VARIABLES_WRITE
23 | CAP_FILESYSTEM
24 | CAP_IO
25 | CAP_HOOK_PRE_EXEC
26 | CAP_HOOK_POST_EXEC
27 | CAP_HOOK_ON_CD
28 | CAP_HOOK_PRE_PROMPT
29 | CAP_FILES_READ
30 | CAP_FILES_WRITE
31 | CAP_COMMANDS_EXEC;
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
34pub enum Capability {
35 VariablesRead,
36 VariablesWrite,
37 Filesystem,
38 Io,
39 HookPreExec,
40 HookPostExec,
41 HookOnCd,
42 HookPrePrompt,
43 FilesRead,
44 FilesWrite,
45 CommandsExec,
46}
47
48impl Capability {
49 pub fn to_bitflag(self) -> u32 {
50 match self {
51 Capability::VariablesRead => CAP_VARIABLES_READ,
52 Capability::VariablesWrite => CAP_VARIABLES_WRITE,
53 Capability::Filesystem => CAP_FILESYSTEM,
54 Capability::Io => CAP_IO,
55 Capability::HookPreExec => CAP_HOOK_PRE_EXEC,
56 Capability::HookPostExec => CAP_HOOK_POST_EXEC,
57 Capability::HookOnCd => CAP_HOOK_ON_CD,
58 Capability::HookPrePrompt => CAP_HOOK_PRE_PROMPT,
59 Capability::FilesRead => CAP_FILES_READ,
60 Capability::FilesWrite => CAP_FILES_WRITE,
61 Capability::CommandsExec => CAP_COMMANDS_EXEC,
62 }
63 }
64
65 pub fn as_str(self) -> &'static str {
66 match self {
67 Capability::VariablesRead => "variables:read",
68 Capability::VariablesWrite => "variables:write",
69 Capability::Filesystem => "filesystem",
70 Capability::Io => "io",
71 Capability::HookPreExec => "hooks:pre_exec",
72 Capability::HookPostExec => "hooks:post_exec",
73 Capability::HookOnCd => "hooks:on_cd",
74 Capability::HookPrePrompt => "hooks:pre_prompt",
75 Capability::FilesRead => "files:read",
76 Capability::FilesWrite => "files:write",
77 Capability::CommandsExec => "commands:exec",
78 }
79 }
80}
81
82pub fn parse_capability(s: &str) -> Option<Capability> {
85 Some(match s {
86 "variables:read" => Capability::VariablesRead,
87 "variables:write" => Capability::VariablesWrite,
88 "filesystem" => Capability::Filesystem,
89 "io" => Capability::Io,
90 "hooks:pre_exec" => Capability::HookPreExec,
91 "hooks:post_exec" => Capability::HookPostExec,
92 "hooks:on_cd" => Capability::HookOnCd,
93 "hooks:pre_prompt" => Capability::HookPrePrompt,
94 "files:read" => Capability::FilesRead,
95 "files:write" => Capability::FilesWrite,
96 "commands:exec" => Capability::CommandsExec,
97 _ => return None,
98 })
99}
100
101pub fn capabilities_to_bitflags(caps: &[Capability]) -> u32 {
103 caps.iter().fold(0u32, |acc, c| acc | c.to_bitflag())
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn parse_known_strings() {
112 assert_eq!(parse_capability("io"), Some(Capability::Io));
113 assert_eq!(
114 parse_capability("hooks:pre_prompt"),
115 Some(Capability::HookPrePrompt)
116 );
117 }
118
119 #[test]
120 fn parse_unknown_returns_none() {
121 assert_eq!(parse_capability("variables:execute"), None);
122 assert_eq!(parse_capability(""), None);
123 }
124
125 #[test]
126 fn capability_round_trip() {
127 for cap in [
128 Capability::VariablesRead,
129 Capability::VariablesWrite,
130 Capability::Filesystem,
131 Capability::Io,
132 Capability::HookPreExec,
133 Capability::HookPostExec,
134 Capability::HookOnCd,
135 Capability::HookPrePrompt,
136 Capability::FilesRead,
137 Capability::FilesWrite,
138 Capability::CommandsExec,
139 ] {
140 assert_eq!(parse_capability(cap.as_str()), Some(cap));
141 }
142 }
143
144 #[test]
145 fn cap_all_covers_every_variant() {
146 let bits = capabilities_to_bitflags(&[
147 Capability::VariablesRead,
148 Capability::VariablesWrite,
149 Capability::Filesystem,
150 Capability::Io,
151 Capability::HookPreExec,
152 Capability::HookPostExec,
153 Capability::HookOnCd,
154 Capability::HookPrePrompt,
155 Capability::FilesRead,
156 Capability::FilesWrite,
157 Capability::CommandsExec,
158 ]);
159 assert_eq!(bits, CAP_ALL);
160 }
161
162 #[test]
163 fn parse_files_capabilities() {
164 assert_eq!(parse_capability("files:read"), Some(Capability::FilesRead));
165 assert_eq!(
166 parse_capability("files:write"),
167 Some(Capability::FilesWrite)
168 );
169 }
170
171 #[test]
172 fn files_capabilities_round_trip() {
173 for cap in [Capability::FilesRead, Capability::FilesWrite] {
174 assert_eq!(parse_capability(cap.as_str()), Some(cap));
175 }
176 }
177
178 #[test]
179 fn cap_all_includes_files_bits() {
180 assert_eq!(CAP_ALL & CAP_FILES_READ, CAP_FILES_READ);
181 assert_eq!(CAP_ALL & CAP_FILES_WRITE, CAP_FILES_WRITE);
182 }
183
184 #[test]
185 fn parse_commands_exec_capability() {
186 assert_eq!(
187 parse_capability("commands:exec"),
188 Some(Capability::CommandsExec)
189 );
190 }
191
192 #[test]
193 fn commands_exec_capability_round_trip() {
194 assert_eq!(
195 parse_capability(Capability::CommandsExec.as_str()),
196 Some(Capability::CommandsExec)
197 );
198 assert_eq!(Capability::CommandsExec.as_str(), "commands:exec");
199 assert_eq!(Capability::CommandsExec.to_bitflag(), CAP_COMMANDS_EXEC);
200 }
201
202 #[test]
203 fn cap_all_includes_commands_exec_bit() {
204 assert_eq!(CAP_ALL & CAP_COMMANDS_EXEC, CAP_COMMANDS_EXEC);
205 }
206}
207
208pub mod pattern;