Skip to main content

rust_serv/file_upload/
handler.rs

1//! Upload handler
2
3use std::path::PathBuf;
4use std::io::Write;
5
6use super::config::UploadConfig;
7use super::multipart::MultipartParser;
8
9/// Upload result
10#[derive(Debug, Clone, PartialEq)]
11pub enum UploadResult {
12    /// Upload successful
13    Success {
14        filename: String,
15        path: PathBuf,
16        size: usize,
17    },
18    /// File too large
19    TooLarge {
20        filename: String,
21        size: usize,
22        max_size: usize,
23    },
24    /// Extension not allowed
25    ExtensionNotAllowed {
26        filename: String,
27    },
28    /// File already exists
29    AlreadyExists {
30        filename: String,
31    },
32    /// Write error
33    WriteError {
34        filename: String,
35        error: String,
36    },
37    /// Invalid request
38    InvalidRequest(String),
39}
40
41impl UploadResult {
42    /// Check if upload was successful
43    pub fn is_success(&self) -> bool {
44        matches!(self, UploadResult::Success { .. })
45    }
46}
47
48/// Upload handler
49pub struct UploadHandler {
50    config: UploadConfig,
51}
52
53impl UploadHandler {
54    /// Create a new upload handler
55    pub fn new(config: UploadConfig) -> Self {
56        Self { config }
57    }
58
59    /// Get configuration
60    pub fn config(&self) -> &UploadConfig {
61        &self.config
62    }
63
64    /// Handle file upload
65    pub fn handle_upload(&self, filename: &str, data: &[u8]) -> UploadResult {
66        // Check file size
67        if !self.config.is_size_allowed(data.len()) {
68            return UploadResult::TooLarge {
69                filename: filename.to_string(),
70                size: data.len(),
71                max_size: self.config.max_file_size,
72            };
73        }
74
75        // Check extension
76        if !self.config.is_extension_allowed(filename) {
77            return UploadResult::ExtensionNotAllowed {
78                filename: filename.to_string(),
79            };
80        }
81
82        // Generate final filename
83        let final_filename = self.config.generate_unique_filename(filename);
84        let file_path = self.config.upload_dir.join(&final_filename);
85
86        // Check if file exists
87        if !self.config.overwrite && file_path.exists() {
88            return UploadResult::AlreadyExists {
89                filename: final_filename,
90            };
91        }
92
93        // Create upload directory if needed
94        if let Some(parent) = file_path.parent() {
95            if let Err(e) = std::fs::create_dir_all(parent) {
96                return UploadResult::WriteError {
97                    filename: final_filename,
98                    error: e.to_string(),
99                };
100            }
101        }
102
103        // Write file
104        match std::fs::File::create(&file_path) {
105            Ok(mut file) => {
106                if let Err(e) = file.write_all(data) {
107                    return UploadResult::WriteError {
108                        filename: final_filename,
109                        error: e.to_string(),
110                    };
111                }
112                UploadResult::Success {
113                    filename: final_filename,
114                    path: file_path,
115                    size: data.len(),
116                }
117            }
118            Err(e) => UploadResult::WriteError {
119                filename: final_filename,
120                error: e.to_string(),
121            },
122        }
123    }
124
125    /// Handle multipart upload
126    pub fn handle_multipart(&self, content_type: &str, data: &[u8]) -> Vec<(String, UploadResult)> {
127        let parser = match MultipartParser::from_content_type(content_type) {
128            Some(p) => p,
129            None => return vec![("".to_string(), UploadResult::InvalidRequest("Invalid Content-Type".to_string()))],
130        };
131
132        let parts = match parser.parse(data) {
133            Ok(p) => p,
134            Err(e) => return vec![("".to_string(), UploadResult::InvalidRequest(e))],
135        };
136
137        parts
138            .into_iter()
139            .filter(|p| p.is_file())
140            .map(|part| {
141                let filename = part.filename.unwrap_or_default();
142                let result = self.handle_upload(&filename, &part.data);
143                (filename, result)
144            })
145            .collect()
146    }
147
148    /// Delete an uploaded file
149    pub fn delete(&self, filename: &str) -> Result<(), String> {
150        let file_path = self.config.upload_dir.join(filename);
151        
152        if !file_path.exists() {
153            return Err("File not found".to_string());
154        }
155
156        std::fs::remove_file(&file_path)
157            .map_err(|e| e.to_string())
158    }
159
160    /// List uploaded files
161    pub fn list(&self) -> Vec<String> {
162        let mut files = Vec::new();
163        
164        if let Ok(entries) = std::fs::read_dir(&self.config.upload_dir) {
165            for entry in entries.flatten() {
166                if entry.path().is_file() {
167                    if let Some(name) = entry.file_name().to_str() {
168                        files.push(name.to_string());
169                    }
170                }
171            }
172        }
173        
174        files.sort();
175        files
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182    use tempfile::TempDir;
183
184    fn create_test_handler() -> (UploadHandler, TempDir) {
185        let dir = TempDir::new().unwrap();
186        let config = UploadConfig::new(dir.path());
187        (UploadHandler::new(config), dir)
188    }
189
190    #[test]
191    fn test_handler_creation() {
192        let (handler, _dir) = create_test_handler();
193        assert_eq!(handler.config().max_file_size, 100 * 1024 * 1024);
194    }
195
196    #[test]
197    fn test_upload_success() {
198        let (handler, dir) = create_test_handler();
199        
200        let result = handler.handle_upload("test.txt", b"Hello World");
201        
202        if let UploadResult::Success { filename, path, size } = result {
203            assert_eq!(filename, "test.txt");
204            assert!(path.starts_with(dir.path()));
205            assert_eq!(size, 11);
206        } else {
207            panic!("Expected Success result");
208        }
209    }
210
211    #[test]
212    fn test_upload_too_large() {
213        let dir = TempDir::new().unwrap();
214        let config = UploadConfig::new(dir.path()).with_max_size(10);
215        let handler = UploadHandler::new(config);
216        
217        let result = handler.handle_upload("test.txt", b"Hello World!");
218        
219        if let UploadResult::TooLarge { size, max_size, .. } = result {
220            assert_eq!(size, 12);
221            assert_eq!(max_size, 10);
222        } else {
223            panic!("Expected TooLarge result");
224        }
225    }
226
227    #[test]
228    fn test_upload_extension_not_allowed() {
229        let dir = TempDir::new().unwrap();
230        let config = UploadConfig::new(dir.path())
231            .with_extensions(vec!["txt".to_string()]);
232        let handler = UploadHandler::new(config);
233        
234        let result = handler.handle_upload("test.exe", b"data");
235        
236        assert!(matches!(result, UploadResult::ExtensionNotAllowed { .. }));
237    }
238
239    #[test]
240    fn test_upload_already_exists() {
241        let (handler, _dir) = create_test_handler();
242        
243        // First upload
244        handler.handle_upload("test.txt", b"data1");
245        
246        // Second upload with same name
247        let result = handler.handle_upload("test.txt", b"data2");
248        
249        assert!(matches!(result, UploadResult::AlreadyExists { .. }));
250    }
251
252    #[test]
253    fn test_upload_overwrite() {
254        let dir = TempDir::new().unwrap();
255        let config = UploadConfig::new(dir.path()).with_overwrite(true);
256        let handler = UploadHandler::new(config);
257        
258        // First upload
259        handler.handle_upload("test.txt", b"data1");
260        
261        // Second upload with same name (should overwrite)
262        let result = handler.handle_upload("test.txt", b"data2");
263        
264        assert!(result.is_success());
265        
266        // Verify content was overwritten
267        let content = std::fs::read_to_string(dir.path().join("test.txt")).unwrap();
268        assert_eq!(content, "data2");
269    }
270
271    #[test]
272    fn test_upload_unique_names() {
273        let dir = TempDir::new().unwrap();
274        let config = UploadConfig::new(dir.path()).with_unique_names(true);
275        let handler = UploadHandler::new(config);
276        
277        let result1 = handler.handle_upload("test.txt", b"data1");
278        let result2 = handler.handle_upload("test.txt", b"data2");
279        
280        if let (UploadResult::Success { filename: f1, .. }, UploadResult::Success { filename: f2, .. }) = (result1, result2) {
281            assert_ne!(f1, f2);
282            assert!(f1.starts_with("test_"));
283            assert!(f2.starts_with("test_"));
284        } else {
285            panic!("Expected Success results");
286        }
287    }
288
289    #[test]
290    fn test_upload_create_directory() {
291        let dir = TempDir::new().unwrap();
292        let upload_path = dir.path().join("uploads").join("nested");
293        let config = UploadConfig::new(upload_path);
294        let handler = UploadHandler::new(config);
295        
296        let result = handler.handle_upload("test.txt", b"data");
297        
298        assert!(result.is_success());
299    }
300
301    #[test]
302    fn test_delete() {
303        let (handler, _dir) = create_test_handler();
304        
305        handler.handle_upload("test.txt", b"data");
306        
307        let result = handler.delete("test.txt");
308        assert!(result.is_ok());
309        
310        // File should be gone
311        let result = handler.delete("test.txt");
312        assert!(result.is_err());
313    }
314
315    #[test]
316    fn test_delete_nonexistent() {
317        let (handler, _dir) = create_test_handler();
318        
319        let result = handler.delete("nonexistent.txt");
320        assert!(result.is_err());
321    }
322
323    #[test]
324    fn test_list() {
325        let (handler, _dir) = create_test_handler();
326        
327        handler.handle_upload("a.txt", b"data");
328        handler.handle_upload("b.txt", b"data");
329        handler.handle_upload("c.txt", b"data");
330        
331        let files = handler.list();
332        
333        assert_eq!(files.len(), 3);
334        assert_eq!(files, vec!["a.txt", "b.txt", "c.txt"]);
335    }
336
337    #[test]
338    fn test_list_empty() {
339        let (handler, _dir) = create_test_handler();
340        
341        let files = handler.list();
342        assert!(files.is_empty());
343    }
344
345    #[test]
346    fn test_upload_result_is_success() {
347        let success = UploadResult::Success {
348            filename: "test.txt".to_string(),
349            path: PathBuf::from("/uploads/test.txt"),
350            size: 100,
351        };
352        assert!(success.is_success());
353        
354        let too_large = UploadResult::TooLarge {
355            filename: "test.txt".to_string(),
356            size: 200,
357            max_size: 100,
358        };
359        assert!(!too_large.is_success());
360    }
361
362    #[test]
363    fn test_handle_multipart() {
364        let (handler, _dir) = create_test_handler();
365        
366        let content_type = "multipart/form-data; boundary=boundary";
367        let data = b"--boundary\r\n\
368            Content-Disposition: form-data; name=\"file\"; filename=\"test.txt\"\r\n\
369            Content-Type: text/plain\r\n\
370            \r\n\
371            Hello World\r\n\
372            --boundary--";
373        
374        let results = handler.handle_multipart(content_type, data);
375        
376        assert_eq!(results.len(), 1);
377        assert!(results[0].1.is_success());
378    }
379
380    #[test]
381    fn test_handle_multipart_multiple() {
382        let (handler, _dir) = create_test_handler();
383        
384        let content_type = "multipart/form-data; boundary=boundary";
385        let data = b"--boundary\r\n\
386            Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\"\r\n\
387            \r\n\
388            content1\r\n\
389            --boundary\r\n\
390            Content-Disposition: form-data; name=\"file2\"; filename=\"b.txt\"\r\n\
391            \r\n\
392            content2\r\n\
393            --boundary--";
394        
395        let results = handler.handle_multipart(content_type, data);
396        
397        assert_eq!(results.len(), 2);
398        assert!(results[0].1.is_success());
399        assert!(results[1].1.is_success());
400    }
401
402    #[test]
403    fn test_handle_multipart_invalid_content_type() {
404        let (handler, _dir) = create_test_handler();
405        
406        let results = handler.handle_multipart("application/json", b"data");
407        
408        assert_eq!(results.len(), 1);
409        assert!(matches!(results[0].1, UploadResult::InvalidRequest(_)));
410    }
411}