aster/sandbox/
filesystem.rs1use serde::{Deserialize, Serialize};
6use std::path::{Path, PathBuf};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10pub enum PathPermission {
11 ReadOnly,
13 ReadWrite,
15 Denied,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct PathRule {
22 pub pattern: String,
24 pub permission: PathPermission,
26 pub recursive: bool,
28}
29
30impl PathRule {
31 pub fn read_only(pattern: impl Into<String>) -> Self {
33 Self {
34 pattern: pattern.into(),
35 permission: PathPermission::ReadOnly,
36 recursive: true,
37 }
38 }
39
40 pub fn read_write(pattern: impl Into<String>) -> Self {
42 Self {
43 pattern: pattern.into(),
44 permission: PathPermission::ReadWrite,
45 recursive: true,
46 }
47 }
48
49 pub fn denied(pattern: impl Into<String>) -> Self {
51 Self {
52 pattern: pattern.into(),
53 permission: PathPermission::Denied,
54 recursive: true,
55 }
56 }
57
58 pub fn matches(&self, path: &Path) -> bool {
60 let pattern_path = Path::new(&self.pattern);
61
62 if self.recursive {
63 path.starts_with(pattern_path)
64 } else {
65 path == pattern_path
66 }
67 }
68}
69
70#[derive(Debug, Clone, Default, Serialize, Deserialize)]
72pub struct FilesystemPolicy {
73 pub rules: Vec<PathRule>,
75 pub default_permission: Option<PathPermission>,
77}
78
79impl FilesystemPolicy {
80 pub fn new() -> Self {
82 Self::default()
83 }
84
85 pub fn add_rule(&mut self, rule: PathRule) {
87 self.rules.push(rule);
88 }
89
90 pub fn get_permission(&self, path: &Path) -> PathPermission {
92 for rule in self.rules.iter().rev() {
94 if rule.matches(path) {
95 return rule.permission;
96 }
97 }
98
99 self.default_permission.unwrap_or(PathPermission::Denied)
100 }
101
102 pub fn can_read(&self, path: &Path) -> bool {
104 matches!(
105 self.get_permission(path),
106 PathPermission::ReadOnly | PathPermission::ReadWrite
107 )
108 }
109
110 pub fn can_write(&self, path: &Path) -> bool {
112 self.get_permission(path) == PathPermission::ReadWrite
113 }
114}
115
116pub struct FilesystemSandbox {
118 policy: FilesystemPolicy,
120 root: PathBuf,
122 enabled: bool,
124}
125
126impl FilesystemSandbox {
127 pub fn new(root: PathBuf) -> Self {
129 Self {
130 policy: FilesystemPolicy::default(),
131 root,
132 enabled: true,
133 }
134 }
135
136 pub fn with_policy(root: PathBuf, policy: FilesystemPolicy) -> Self {
138 Self {
139 policy,
140 root,
141 enabled: true,
142 }
143 }
144
145 pub fn set_enabled(&mut self, enabled: bool) {
147 self.enabled = enabled;
148 }
149
150 pub fn is_within_sandbox(&self, path: &Path) -> bool {
152 path.starts_with(&self.root)
153 }
154
155 pub fn normalize_path(&self, path: &Path) -> anyhow::Result<PathBuf> {
157 let normalized = if path.is_absolute() {
158 path.to_path_buf()
159 } else {
160 self.root.join(path)
161 };
162
163 if !self.is_within_sandbox(&normalized) {
165 anyhow::bail!("路径 {} 不在沙箱范围内", path.display());
166 }
167
168 Ok(normalized)
169 }
170
171 pub fn check_read(&self, path: &Path) -> anyhow::Result<()> {
173 if !self.enabled {
174 return Ok(());
175 }
176
177 let normalized = self.normalize_path(path)?;
178
179 if !self.policy.can_read(&normalized) {
180 anyhow::bail!("没有读取权限: {}", path.display());
181 }
182
183 Ok(())
184 }
185
186 pub fn check_write(&self, path: &Path) -> anyhow::Result<()> {
188 if !self.enabled {
189 return Ok(());
190 }
191
192 let normalized = self.normalize_path(path)?;
193
194 if !self.policy.can_write(&normalized) {
195 anyhow::bail!("没有写入权限: {}", path.display());
196 }
197
198 Ok(())
199 }
200
201 pub fn policy(&self) -> &FilesystemPolicy {
203 &self.policy
204 }
205
206 pub fn policy_mut(&mut self) -> &mut FilesystemPolicy {
208 &mut self.policy
209 }
210
211 pub fn root(&self) -> &Path {
213 &self.root
214 }
215}