1use crate::Error;
5use crate::utils::Utils;
6use anyhow::Result;
7use std::fs;
8use std::path::{Path, PathBuf};
9use tracing::warn;
10use url::Url;
11
12pub struct SecurityValidator {
14 pub allowed_schemes: Vec<String>,
16 pub max_file_size: u64,
18 pub allowed_extensions: Vec<String>,
20 pub root_directory: Option<PathBuf>,
22}
23
24impl Default for SecurityValidator {
25 fn default() -> Self {
26 Self {
27 allowed_schemes: vec!["file".to_string(), "http".to_string(), "https".to_string()],
28 max_file_size: 10 * 1024 * 1024, allowed_extensions: vec![
30 "txt".to_string(),
31 "md".to_string(),
32 "json".to_string(),
33 "yml".to_string(),
34 "yaml".to_string(),
35 "xml".to_string(),
36 "csv".to_string(),
37 "log".to_string(),
38 ],
39 root_directory: None,
40 }
41 }
42}
43
44impl SecurityValidator {
45 pub fn new() -> Self {
47 Self::default()
48 }
49
50 pub fn with_allowed_schemes(mut self, schemes: Vec<String>) -> Self {
52 self.allowed_schemes = schemes;
53 self
54 }
55
56 pub fn with_max_file_size(mut self, size: u64) -> Self {
58 self.max_file_size = size;
59 self
60 }
61
62 pub fn with_allowed_extensions(mut self, extensions: Vec<String>) -> Self {
64 self.allowed_extensions = extensions;
65 self
66 }
67
68 pub fn with_root_directory(mut self, root: PathBuf) -> Self {
70 self.root_directory = Some(root);
71 self
72 }
73
74 pub fn validate_uri(&self, uri: &Url) -> Result<()> {
76 if !self.allowed_schemes.contains(&uri.scheme().to_string()) {
78 return Err(Error::security(format!(
79 "URI scheme '{}' is not allowed. Allowed schemes: {:?}",
80 uri.scheme(),
81 self.allowed_schemes
82 ))
83 .into());
84 }
85
86 if uri.scheme() == "file"
88 && let Ok(path) = uri.to_file_path()
89 {
90 self.validate_file_path(&path)?;
91 }
92
93 Ok(())
94 }
95
96 pub fn validate_file_path(&self, path: &Path) -> Result<()> {
98 let sanitized = Utils::sanitize_path(path);
100
101 if let Some(root) = &self.root_directory {
103 let canonical_root = fs::canonicalize(root).map_err(|e| {
104 Error::security(format!("Failed to canonicalize root directory: {}", e))
105 })?;
106
107 let canonical_path = fs::canonicalize(&sanitized)
108 .map_err(|e| Error::security(format!("Failed to canonicalize file path: {}", e)))?;
109
110 if !canonical_path.starts_with(&canonical_root) {
111 return Err(Error::security(format!(
112 "Path '{}' is outside of allowed directory '{}'",
113 canonical_path.display(),
114 canonical_root.display()
115 ))
116 .into());
117 }
118 }
119
120 if let Some(extension) = path.extension() {
122 let ext_str = extension.to_string_lossy().to_lowercase();
123 if !self.allowed_extensions.contains(&ext_str) {
124 warn!(
125 "File extension '{}' is not in allowed list: {:?}",
126 ext_str, self.allowed_extensions
127 );
128 return Err(Error::security(format!(
129 "File extension '{}' is not allowed. Allowed extensions: {:?}",
130 ext_str, self.allowed_extensions
131 ))
132 .into());
133 }
134 }
135
136 Ok(())
137 }
138
139 pub fn validate_file_path_str(&self, path: &str) -> Result<()> {
141 self.validate_file_path(Path::new(path))
142 }
143
144 pub fn validate_file_path_buf(&self, path: &Path) -> Result<()> {
146 self.validate_file_path(path)
147 }
148
149 pub fn validate_file_size(&self, size: u64) -> Result<()> {
151 Ok(Utils::validate_file_size(size, self.max_file_size)?)
152 }
153
154 pub fn validate_string_input(&self, input: &str) -> Result<()> {
156 Ok(Utils::validate_safe_string(input)?)
157 }
158
159 pub fn validate_resource_access(&self, uri: &Url) -> Result<()> {
161 self.validate_uri(uri)?;
163
164 if uri.scheme() == "file"
166 && let Ok(path) = uri.to_file_path()
167 {
168 if !path.exists() {
170 return Err(
171 Error::not_found(format!("File does not exist: {}", path.display())).into(),
172 );
173 }
174
175 if let Ok(metadata) = fs::metadata(&path) {
177 self.validate_file_size(metadata.len())?;
178 } else {
179 return Err(Error::security(format!(
180 "Cannot read file metadata: {}",
181 path.display()
182 ))
183 .into());
184 }
185 }
186 Ok(())
187 }
188
189 pub fn validate_tool_parameters(&self, params: &serde_json::Value) -> Result<()> {
191 match params {
193 serde_json::Value::String(s) => {
194 self.validate_string_input(s)?;
195 }
196 serde_json::Value::Object(obj) => {
197 for (key, value) in obj {
198 self.validate_string_input(key)?;
199 self.validate_tool_parameters(value)?;
200 }
201 }
202 serde_json::Value::Array(arr) => {
203 for value in arr {
204 self.validate_tool_parameters(value)?;
205 }
206 }
207 _ => {} }
209
210 Ok(())
211 }
212}
213
214pub struct ErrorRecoverySystem {
216 pub max_retries: u32,
218 pub retry_delay_ms: u64,
220 pub log_errors: bool,
222}
223
224impl Default for ErrorRecoverySystem {
225 fn default() -> Self {
226 Self {
227 max_retries: 3,
228 retry_delay_ms: 1000,
229 log_errors: true,
230 }
231 }
232}
233
234impl ErrorRecoverySystem {
235 pub fn new() -> Self {
237 Self::default()
238 }
239
240 pub async fn execute_with_retry<F, T, E>(&self, mut operation: F) -> Result<T>
242 where
243 F: FnMut() -> Result<T, E> + Send + Sync,
244 E: std::error::Error + Send + Sync + 'static,
245 T: Send + Sync,
246 {
247 let mut attempts = 0;
248
249 loop {
250 match operation() {
251 Ok(result) => return Ok(result),
252 Err(e) => {
253 attempts += 1;
254
255 if self.log_errors {
256 warn!(
257 "Operation failed (attempt {}/{}): {}",
258 attempts, self.max_retries, e
259 );
260 }
261
262 if attempts >= self.max_retries {
263 return Err(Error::operation_failed(format!(
264 "Operation failed after {} attempts: {}",
265 self.max_retries, e
266 ))
267 .into());
268 }
269
270 tokio::time::sleep(tokio::time::Duration::from_millis(self.retry_delay_ms))
272 .await;
273 }
274 }
275 }
276 }
277
278 pub fn handle_invalid_method(&self, method: &str) -> Error {
280 if self.log_errors {
281 warn!("Invalid method called: {}", method);
282 }
283
284 Error::method_not_found(format!(
285 "Method '{}' is not supported. Available methods should be checked through capability negotiation.",
286 method
287 ))
288 }
289
290 pub fn handle_invalid_parameters(&self, method: &str, error: &str) -> Error {
292 if self.log_errors {
293 warn!("Invalid parameters for method '{}': {}", method, error);
294 }
295
296 Error::invalid_params(format!(
297 "Invalid parameters for method '{}': {}. Please check the method signature and required parameters.",
298 method, error
299 ))
300 }
301
302 pub fn handle_resource_error(&self, uri: &str, error: &str) -> Error {
304 if self.log_errors {
305 warn!("Resource access error for '{}': {}", uri, error);
306 }
307
308 Error::resource_error(format!(
309 "Failed to access resource '{}': {}. Please check the resource exists and is accessible.",
310 uri, error
311 ))
312 }
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318 use std::fs;
319 use tempfile::TempDir;
320
321 #[test]
322 fn test_security_validator_path_traversal() {
323 let temp_dir = TempDir::new().unwrap();
324 let validator = SecurityValidator::new().with_root_directory(temp_dir.path().to_path_buf());
325
326 let allowed_file = temp_dir.path().join("allowed.txt");
328 fs::write(&allowed_file, "test content").unwrap();
329
330 assert!(validator.validate_file_path(&allowed_file).is_ok());
332
333 let traversal_attempts = vec![
335 temp_dir.path().join("../../../etc/passwd"),
336 temp_dir.path().join("../../sensitive.txt"),
337 temp_dir.path().join("../outside/file.txt"),
338 ];
339
340 for malicious_path in traversal_attempts {
341 assert!(validator.validate_file_path(&malicious_path).is_err());
342 }
343 }
344
345 #[test]
346 fn test_security_validator_file_extensions() {
347 let validator = SecurityValidator::new()
348 .with_allowed_extensions(vec!["txt".to_string(), "md".to_string()]);
349
350 assert!(validator.validate_file_path(Path::new("test.txt")).is_ok());
352 assert!(validator.validate_file_path(Path::new("test.md")).is_ok());
353
354 assert!(validator.validate_file_path(Path::new("test.exe")).is_err());
356 assert!(validator.validate_file_path(Path::new("test.sh")).is_err());
357 }
358
359 #[tokio::test]
360 async fn test_error_recovery_system() {
361 let recovery = ErrorRecoverySystem::new();
362
363 let result = recovery
365 .execute_with_retry(|| -> Result<i32, std::io::Error> { Ok(42) })
366 .await;
367
368 assert!(result.is_ok());
369 assert_eq!(result.unwrap(), 42);
370
371 let attempts = std::sync::Arc::new(std::sync::Mutex::new(0));
373 let attempts_clone = attempts.clone();
374 let result = recovery
375 .execute_with_retry(move || -> Result<i32, std::io::Error> {
376 let mut attempts_ref = attempts_clone.lock().unwrap();
377 *attempts_ref += 1;
378 if *attempts_ref < 3 {
379 Err(std::io::Error::other("temporary failure"))
380 } else {
381 Ok(42)
382 }
383 })
384 .await;
385
386 assert!(result.is_ok());
387 assert_eq!(result.unwrap(), 42);
388 }
389}