| |
| |
| |
|
|
| use std::fs; |
| use std::path::{Path, PathBuf}; |
| use tracing::info; |
|
|
| |
| #[derive(Debug)] |
| pub struct ReadResult { |
| pub path: String, |
| pub content: String, |
| pub success: bool, |
| pub error: Option<String>, |
| pub lines: usize, |
| } |
|
|
| |
| pub struct FileReader { |
| |
| allowed_dirs: Vec<PathBuf>, |
| } |
|
|
| impl FileReader { |
| |
| pub fn new() -> Self { |
| let allowed_dirs = vec![ |
| PathBuf::from("/home/zach/.openclaw/workspace"), |
| PathBuf::from("/home/zach"), |
| PathBuf::from("/home/zach/Documents"), |
| PathBuf::from("/home/zach/Downloads"), |
| PathBuf::from("/home/zach/Desktop"), |
| ]; |
| Self { allowed_dirs } |
| } |
|
|
| |
| fn is_allowed(&self, path: &Path) -> bool { |
| |
| let resolved = fs::canonicalize(path).ok(); |
| let check_path = resolved.as_deref().unwrap_or(path); |
| |
| self.allowed_dirs.iter().any(|dir| { |
| check_path.starts_with(dir) || check_path == dir |
| }) |
| } |
|
|
| |
| pub fn read(&self, path: &str) -> ReadResult { |
| let path = PathBuf::from(path); |
| |
| |
| if !self.is_allowed(&path) { |
| return ReadResult { |
| path: path.display().to_string(), |
| content: String::new(), |
| success: false, |
| error: Some("Path not allowed. Can only read files in workspace, home, Documents, Downloads, Desktop.".to_string()), |
| lines: 0, |
| }; |
| } |
| |
| |
| if !path.exists() { |
| return ReadResult { |
| path: path.display().to_string(), |
| content: String::new(), |
| success: false, |
| error: Some("File not found.".to_string()), |
| lines: 0, |
| }; |
| } |
| |
| |
| if !path.is_file() { |
| return ReadResult { |
| path: path.display().to_string(), |
| content: String::new(), |
| success: false, |
| error: Some("Path is a directory, not a file.".to_string()), |
| lines: 0, |
| }; |
| } |
| |
| |
| match fs::read_to_string(&path) { |
| Ok(content) => { |
| let lines = content.lines().count(); |
| info!("Read file: {} ({} lines)", path.display(), lines); |
| ReadResult { |
| path: path.display().to_string(), |
| content, |
| success: true, |
| error: None, |
| lines, |
| } |
| } |
| Err(e) => ReadResult { |
| path: path.display().to_string(), |
| content: String::new(), |
| success: false, |
| error: Some(format!("Cannot read: {}. Do you have permission?", e)), |
| lines: 0, |
| }, |
| } |
| } |
|
|
| |
| pub fn list_dir(&self, dir_path: &str) -> Result<Vec<String>, String> { |
| let path = PathBuf::from(dir_path); |
| |
| if !self.is_allowed(&path) { |
| return Err("Path not allowed.".to_string()); |
| } |
| |
| if !path.exists() { |
| return Err("Directory not found.".to_string()); |
| } |
| |
| if !path.is_dir() { |
| return Err("Path is not a directory.".to_string()); |
| } |
| |
| let mut entries = Vec::new(); |
| match fs::read_dir(&path) { |
| Ok(dir) => { |
| for entry in dir.filter_map(|e| e.ok()) { |
| let file_name = entry.file_name().to_string_lossy().into_owned(); |
| let file_type = if entry.path().is_dir() { "/" } else { "" }; |
| entries.push(format!("{}{}", file_name, file_type)); |
| } |
| Ok(entries) |
| } |
| Err(e) => Err(format!("Cannot read directory: {}", e)), |
| } |
| } |
|
|
| |
| pub fn find_files(&self, dir: &str, pattern: &str) -> Result<Vec<String>, String> { |
| let path = PathBuf::from(dir); |
| |
| if !self.is_allowed(&path) { |
| return Err("Path not allowed.".to_string()); |
| } |
| |
| if !path.exists() || !path.is_dir() { |
| return Err("Directory not found.".to_string()); |
| } |
| |
| let mut matches = Vec::new(); |
| self.search_recursive(&path, pattern, &mut matches)?; |
| Ok(matches) |
| } |
| |
| fn search_recursive(&self, dir: &Path, pattern: &str, results: &mut Vec<String>) -> Result<(), String> { |
| if !self.is_allowed(dir) { |
| return Ok(()); |
| } |
| |
| let entries = fs::read_dir(dir).map_err(|e| e.to_string())?; |
| |
| for entry in entries.filter_map(|e| e.ok()) { |
| let path = entry.path(); |
| let name = entry.file_name().to_string_lossy().to_string(); |
| |
| if path.is_dir() { |
| if !name.starts_with('.') { |
| self.search_recursive(&path, pattern, results)?; |
| } |
| } else if name.to_lowercase().contains(&pattern.to_lowercase()) { |
| results.push(path.display().to_string()); |
| } |
| } |
| |
| Ok(()) |
| } |
| } |
|
|
| impl Default for FileReader { |
| fn default() -> Self { |
| Self::new() |
| } |
| } |
|
|