airsprotocols_mcpserver_filesystem/binary/
processor.rs

1//! Binary file processing coordinator
2
3// Layer 1: Standard library imports
4use std::path::Path;
5
6// Layer 2: Third-party crate imports
7use anyhow::Result;
8
9// Layer 3: Internal module imports
10use crate::binary::format::{FileFormat, FormatDetector};
11use crate::config::settings::BinaryConfig;
12
13/// Main binary file processing coordinator
14/// Security Hardened: Binary processing disabled for security reasons
15#[derive(Debug)]
16pub struct BinaryProcessor {
17    format_detector: FormatDetector,
18    /// Configuration kept for API compatibility but binary processing is disabled
19    #[allow(dead_code)]
20    config: BinaryConfig,
21}
22
23/// Result of binary file processing
24#[derive(Debug, Clone)]
25pub struct ProcessingResult {
26    /// Detected file format
27    pub format: FileFormat,
28    /// Size of processed data
29    pub size: usize,
30    /// Processing metadata (thumbnails, extracted text, etc.)
31    pub metadata: ProcessingMetadata,
32}
33
34/// Metadata from binary processing
35#[derive(Debug, Clone, Default)]
36pub struct ProcessingMetadata {
37    /// Thumbnail data for images (base64 encoded)
38    pub thumbnail: Option<String>,
39    /// Extracted text content
40    pub text_content: Option<String>,
41    /// Image dimensions (width, height)
42    pub dimensions: Option<(u32, u32)>,
43    /// Additional format-specific metadata
44    pub properties: std::collections::HashMap<String, String>,
45}
46
47impl BinaryProcessor {
48    /// Create a new binary processor with configuration
49    pub fn new(config: BinaryConfig) -> Self {
50        Self {
51            format_detector: FormatDetector::new(),
52            config,
53        }
54    }
55
56    /// Process binary file data based on format and configuration
57    /// SECURITY HARDENING: All binary processing is disabled for security
58    pub async fn process_file_data(&self, data: &[u8], path: &Path) -> Result<ProcessingResult> {
59        // Detect file format first
60        let format = self.format_detector.detect_from_bytes(data);
61
62        // SECURITY: Reject all binary file processing
63        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                // Text files are allowed - basic processing only
81                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                // Unknown formats: check if they might be binary
93                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                    // Likely text content - allow basic processing
103                    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    /// Check if a file format can be processed with current configuration
118    /// SECURITY HARDENING: Only text files are allowed for processing
119    pub fn can_process(&self, format: FileFormat) -> bool {
120        match format {
121            // All binary formats are disabled for security
122            FileFormat::Jpeg
123            | FileFormat::Png
124            | FileFormat::Gif
125            | FileFormat::WebP
126            | FileFormat::Tiff
127            | FileFormat::Bmp => false,
128            FileFormat::Pdf => false, // PDF processing disabled for security
129            FileFormat::Text => true, // Only text files are allowed
130            FileFormat::Unknown => false, // Unknown formats rejected for security
131        }
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,       // 1MB
145            binary_processing_disabled: true, // Security hardening - always disabled
146        }
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; // Very small limit
180        let processor = BinaryProcessor::new(config);
181
182        // Create binary data (simulating JPEG content)
183        let large_data = vec![0xFF, 0xD8, 0xFF, 0xE0]; // JPEG header
184        let path = PathBuf::from("large.jpg");
185
186        let result = processor.process_file_data(&large_data, &path).await;
187        assert!(result.is_err());
188        // Should fail due to binary restriction, not size limit
189        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        // Security hardening: All binary formats should be rejected
201        assert!(!processor.can_process(FileFormat::Jpeg));
202        assert!(!processor.can_process(FileFormat::Pdf));
203        assert!(!processor.can_process(FileFormat::Unknown));
204
205        // Only text files are allowed
206        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        // All binary formats should be rejected for security
215        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        // Only text files are allowed
221        assert!(processor.can_process(FileFormat::Text));
222    }
223}