1use std::path::PathBuf;
4use std::io::Write;
5
6use super::config::UploadConfig;
7use super::multipart::MultipartParser;
8
9#[derive(Debug, Clone, PartialEq)]
11pub enum UploadResult {
12 Success {
14 filename: String,
15 path: PathBuf,
16 size: usize,
17 },
18 TooLarge {
20 filename: String,
21 size: usize,
22 max_size: usize,
23 },
24 ExtensionNotAllowed {
26 filename: String,
27 },
28 AlreadyExists {
30 filename: String,
31 },
32 WriteError {
34 filename: String,
35 error: String,
36 },
37 InvalidRequest(String),
39}
40
41impl UploadResult {
42 pub fn is_success(&self) -> bool {
44 matches!(self, UploadResult::Success { .. })
45 }
46}
47
48pub struct UploadHandler {
50 config: UploadConfig,
51}
52
53impl UploadHandler {
54 pub fn new(config: UploadConfig) -> Self {
56 Self { config }
57 }
58
59 pub fn config(&self) -> &UploadConfig {
61 &self.config
62 }
63
64 pub fn handle_upload(&self, filename: &str, data: &[u8]) -> UploadResult {
66 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 if !self.config.is_extension_allowed(filename) {
77 return UploadResult::ExtensionNotAllowed {
78 filename: filename.to_string(),
79 };
80 }
81
82 let final_filename = self.config.generate_unique_filename(filename);
84 let file_path = self.config.upload_dir.join(&final_filename);
85
86 if !self.config.overwrite && file_path.exists() {
88 return UploadResult::AlreadyExists {
89 filename: final_filename,
90 };
91 }
92
93 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 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 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 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 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 handler.handle_upload("test.txt", b"data1");
245
246 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 handler.handle_upload("test.txt", b"data1");
260
261 let result = handler.handle_upload("test.txt", b"data2");
263
264 assert!(result.is_success());
265
266 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 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}