airsprotocols_mcpserver_filesystem/binary/
processor.rs1use std::path::Path;
5
6use anyhow::Result;
8
9use crate::binary::format::{FileFormat, FormatDetector};
11use crate::config::settings::BinaryConfig;
12
13#[derive(Debug)]
16pub struct BinaryProcessor {
17 format_detector: FormatDetector,
18 #[allow(dead_code)]
20 config: BinaryConfig,
21}
22
23#[derive(Debug, Clone)]
25pub struct ProcessingResult {
26 pub format: FileFormat,
28 pub size: usize,
30 pub metadata: ProcessingMetadata,
32}
33
34#[derive(Debug, Clone, Default)]
36pub struct ProcessingMetadata {
37 pub thumbnail: Option<String>,
39 pub text_content: Option<String>,
41 pub dimensions: Option<(u32, u32)>,
43 pub properties: std::collections::HashMap<String, String>,
45}
46
47impl BinaryProcessor {
48 pub fn new(config: BinaryConfig) -> Self {
50 Self {
51 format_detector: FormatDetector::new(),
52 config,
53 }
54 }
55
56 pub async fn process_file_data(&self, data: &[u8], path: &Path) -> Result<ProcessingResult> {
59 let format = self.format_detector.detect_from_bytes(data);
61
62 match format {
64 FileFormat::Jpeg
65 | FileFormat::Png
66 | FileFormat::Gif
67 | FileFormat::WebP
68 | FileFormat::Tiff
69 | FileFormat::Bmp => Err(anyhow::anyhow!(
70 "Binary file processing disabled for security: {} (detected format: {:?})",
71 path.display(),
72 format
73 )),
74 FileFormat::Pdf => Err(anyhow::anyhow!(
75 "PDF processing disabled for security: {} (detected format: {:?})",
76 path.display(),
77 format
78 )),
79 FileFormat::Text => {
80 let result = ProcessingResult {
82 format,
83 size: data.len(),
84 metadata: ProcessingMetadata {
85 text_content: std::str::from_utf8(data).ok().map(|s| s.to_string()),
86 ..Default::default()
87 },
88 };
89 Ok(result)
90 }
91 FileFormat::Unknown => {
92 if data
94 .iter()
95 .any(|&b| b > 127 || (b < 32 && b != b'\n' && b != b'\r' && b != b'\t'))
96 {
97 Err(anyhow::anyhow!(
98 "Unknown binary file processing disabled for security: {}",
99 path.display()
100 ))
101 } else {
102 let result = ProcessingResult {
104 format,
105 size: data.len(),
106 metadata: ProcessingMetadata {
107 text_content: std::str::from_utf8(data).ok().map(|s| s.to_string()),
108 ..Default::default()
109 },
110 };
111 Ok(result)
112 }
113 }
114 }
115 }
116
117 pub fn can_process(&self, format: FileFormat) -> bool {
120 match format {
121 FileFormat::Jpeg
123 | FileFormat::Png
124 | FileFormat::Gif
125 | FileFormat::WebP
126 | FileFormat::Tiff
127 | FileFormat::Bmp => false,
128 FileFormat::Pdf => false, FileFormat::Text => true, FileFormat::Unknown => false, }
132 }
133}
134
135#[cfg(test)]
136#[allow(clippy::unwrap_used)]
137mod tests {
138 use super::*;
139 use crate::config::settings::BinaryConfig;
140 use std::path::PathBuf;
141
142 fn create_test_config() -> BinaryConfig {
143 BinaryConfig {
144 max_file_size: 1024 * 1024, binary_processing_disabled: true, }
147 }
148
149 #[test]
150 fn test_binary_processor_creation() {
151 let config = create_test_config();
152 let processor = BinaryProcessor::new(config);
153 assert!(processor.config.binary_processing_disabled);
154 }
155
156 #[tokio::test]
157 async fn test_process_text_data() {
158 let config = create_test_config();
159 let processor = BinaryProcessor::new(config);
160
161 let text_data = b"Hello, world!";
162 let path = PathBuf::from("test.txt");
163
164 let result = processor.process_file_data(text_data, &path).await;
165 assert!(result.is_ok());
166
167 let result = result.unwrap();
168 assert_eq!(result.format, FileFormat::Text);
169 assert_eq!(result.size, text_data.len());
170 assert_eq!(
171 result.metadata.text_content,
172 Some("Hello, world!".to_string())
173 );
174 }
175
176 #[tokio::test]
177 async fn test_binary_file_rejection_over_size_limit() {
178 let mut config = create_test_config();
179 config.max_file_size = 10; let processor = BinaryProcessor::new(config);
181
182 let large_data = vec![0xFF, 0xD8, 0xFF, 0xE0]; let path = PathBuf::from("large.jpg");
185
186 let result = processor.process_file_data(&large_data, &path).await;
187 assert!(result.is_err());
188 assert!(result
190 .unwrap_err()
191 .to_string()
192 .contains("Binary file processing disabled"));
193 }
194
195 #[test]
196 fn test_can_process_security_policy() {
197 let config = create_test_config();
198 let processor = BinaryProcessor::new(config);
199
200 assert!(!processor.can_process(FileFormat::Jpeg));
202 assert!(!processor.can_process(FileFormat::Pdf));
203 assert!(!processor.can_process(FileFormat::Unknown));
204
205 assert!(processor.can_process(FileFormat::Text));
207 }
208
209 #[test]
210 fn test_can_process_security_hardened() {
211 let config = create_test_config();
212 let processor = BinaryProcessor::new(config);
213
214 assert!(!processor.can_process(FileFormat::Jpeg));
216 assert!(!processor.can_process(FileFormat::Png));
217 assert!(!processor.can_process(FileFormat::Pdf));
218 assert!(!processor.can_process(FileFormat::Unknown));
219
220 assert!(processor.can_process(FileFormat::Text));
222 }
223}