1use std::collections::HashMap;
75use std::fs;
76use std::io::Read;
77use std::path::{Path, PathBuf};
78
79use chrono::Local;
80use md5::{Digest, Md5};
81
82pub mod image;
87pub mod storage;
88pub mod validate;
89
90#[derive(Debug, thiserror::Error)]
96pub enum UploadError {
97 #[error("The file \"{0}\" does not exist")]
99 FileNotFound(String),
100
101 #[error("Could not move the file \"{from}\" to \"{to}\" ({error})")]
103 MoveFailed {
104 from: String,
106 to: String,
108 error: String,
110 },
111
112 #[error("Unable to create the \"{0}\" directory")]
114 DirectoryCreateFailed(String),
115
116 #[error("Unable to write in the \"{0}\" directory")]
118 DirectoryNotWritable(String),
119
120 #[error("{0}")]
122 UploadFailed(String),
123
124 #[error(transparent)]
126 Io(#[from] std::io::Error),
127
128 #[error(transparent)]
130 Persist(#[from] tempfile::PersistError),
131
132 #[error("Invalid file name \"{0}\" — potential path traversal attack")]
134 InvalidFileName(String),
135}
136
137#[derive(Debug, Clone, Copy, PartialEq, Eq)]
145#[repr(i32)]
146pub enum UploadErrCode {
147 Ok = 0,
149 IniSize = 1,
151 FormSize = 2,
153 Partial = 3,
155 NoFile = 4,
157 NoTmpDir = 6,
159 CantWrite = 7,
161}
162
163impl UploadErrCode {
164 pub fn error_message(self) -> &'static str {
174 match self {
175 UploadErrCode::IniSize | UploadErrCode::FormSize => {
176 "upload File size exceeds the maximum value"
177 }
178 UploadErrCode::Partial => "only the portion of file is uploaded",
179 UploadErrCode::NoFile => "no file to uploaded",
180 UploadErrCode::NoTmpDir => "upload temp dir not found",
181 UploadErrCode::CantWrite => "file write error",
182 UploadErrCode::Ok => "unknown upload error",
183 }
184 }
185
186 pub fn from_i32(code: i32) -> Self {
188 match code {
189 0 => UploadErrCode::Ok,
190 1 => UploadErrCode::IniSize,
191 2 => UploadErrCode::FormSize,
192 3 => UploadErrCode::Partial,
193 4 => UploadErrCode::NoFile,
194 6 => UploadErrCode::NoTmpDir,
195 7 => UploadErrCode::CantWrite,
196 _ => UploadErrCode::Ok,
197 }
198 }
199}
200
201#[derive(Debug, Clone, Copy, PartialEq, Eq)]
207pub enum HashAlgo {
208 Md5,
210 Sha1,
212 Sha256,
214 Sha512,
216}
217
218impl HashAlgo {
219 pub fn as_str(self) -> &'static str {
221 match self {
222 HashAlgo::Md5 => "md5",
223 HashAlgo::Sha1 => "sha1",
224 HashAlgo::Sha256 => "sha256",
225 HashAlgo::Sha512 => "sha512",
226 }
227 }
228
229 pub fn parse_algo(s: &str) -> Option<Self> {
234 match s {
235 "md5" => Some(HashAlgo::Md5),
236 "sha1" => Some(HashAlgo::Sha1),
237 "sha256" => Some(HashAlgo::Sha256),
238 "sha512" => Some(HashAlgo::Sha512),
239 _ => None,
240 }
241 }
242}
243
244#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
260pub enum HashNameRule {
261 #[default]
265 Default,
266
267 Hash(HashAlgo),
271}
272
273#[derive(Debug, Clone)]
294pub struct File {
295 path: PathBuf,
297 hash: HashMap<String, String>,
299 hash_name: Option<String>,
301 extension: Option<String>,
303}
304
305impl File {
306 pub fn new<P: AsRef<Path>>(path: P, check_path: bool) -> Result<Self, UploadError> {
310 let path = path.as_ref().to_path_buf();
311 if check_path && !path.is_file() {
312 return Err(UploadError::FileNotFound(
313 path.to_string_lossy().to_string(),
314 ));
315 }
316 Ok(Self {
317 path,
318 hash: HashMap::new(),
319 hash_name: None,
320 extension: None,
321 })
322 }
323
324 pub fn new_unchecked<P: AsRef<Path>>(path: P) -> Self {
326 Self {
327 path: path.as_ref().to_path_buf(),
328 hash: HashMap::new(),
329 hash_name: None,
330 extension: None,
331 }
332 }
333
334 pub fn path(&self) -> &Path {
336 &self.path
337 }
338
339 pub fn path_name(&self) -> String {
341 self.path.to_string_lossy().to_string()
342 }
343
344 pub fn hash(&mut self, algo: HashAlgo) -> Result<String, UploadError> {
356 let key = algo.as_str().to_string();
357 if let Some(h) = self.hash.get(&key) {
358 return Ok(h.clone());
359 }
360 let h = compute_file_hash(&self.path, algo)?;
361 self.hash.insert(key, h.clone());
362 Ok(h)
363 }
364
365 pub fn md5(&mut self) -> Result<String, UploadError> {
367 self.hash(HashAlgo::Md5)
368 }
369
370 pub fn sha1(&mut self) -> Result<String, UploadError> {
372 self.hash(HashAlgo::Sha1)
373 }
374
375 pub fn get_mime(&self) -> Result<String, UploadError> {
380 if let Ok(Some(t)) = infer::get_from_path(&self.path) {
382 return Ok(t.mime_type().to_string());
383 }
384 let mime = mime_guess::from_path(&self.path)
386 .first_or_octet_stream()
387 .to_string();
388 Ok(mime)
389 }
390
391 pub fn move_to<P: AsRef<Path>>(
400 &mut self,
401 directory: P,
402 name: Option<&str>,
403 ) -> Result<File, UploadError> {
404 let target = self.get_target_file(directory.as_ref(), name)?;
405
406 fs::rename(&self.path, &target.path).map_err(|e| UploadError::MoveFailed {
407 from: self.path.to_string_lossy().to_string(),
408 to: target.path.to_string_lossy().to_string(),
409 error: e.to_string(),
410 })?;
411
412 #[cfg(unix)]
414 {
415 use std::os::unix::fs::PermissionsExt;
416 let _ = fs::set_permissions(&target.path, fs::Permissions::from_mode(0o666));
417 }
418
419 Ok(target)
420 }
421
422 fn get_target_file(&self, directory: &Path, name: Option<&str>) -> Result<File, UploadError> {
430 if !directory.is_dir() {
431 fs::create_dir_all(directory).map_err(|_| {
432 UploadError::DirectoryCreateFailed(directory.to_string_lossy().to_string())
433 })?;
434 }
435
436 let file_name = match name {
437 Some(n) => get_name(n)?,
438 None => self
439 .path
440 .file_name()
441 .map(|n| n.to_string_lossy().to_string())
442 .unwrap_or_default(),
443 };
444
445 if file_name == ".." || file_name.contains('/') || file_name.contains('\\') {
447 return Err(UploadError::InvalidFileName(file_name));
448 }
449
450 let target_path = directory.join(&file_name);
451 Ok(File::new_unchecked(&target_path))
452 }
453
454 pub fn extension(&self) -> String {
458 self.path
459 .extension()
460 .map(|e| e.to_string_lossy().to_string())
461 .unwrap_or_default()
462 }
463
464 pub fn set_extension(&mut self, extension: &str) {
466 self.extension = Some(extension.to_string());
467 }
468
469 pub fn hash_name(&mut self, rule: HashNameRule) -> Result<String, UploadError> {
499 if self.hash_name.is_none() {
500 let hash_name = match rule {
501 HashNameRule::Hash(algo) => {
502 let hash = self.hash(algo)?;
504 if hash.len() < 2 {
505 hash
506 } else {
507 format!("{}/{}", &hash[..2], &hash[2..])
508 }
509 }
510 HashNameRule::Default => {
511 let now = Local::now();
513 let date_str = now.format("%Y%m%d").to_string();
514 let secs = now.timestamp();
516 let micros = now.timestamp_subsec_micros();
517 let microtime_str = format!("{}.{:06}", secs, micros);
518 let pathname = self.path.to_string_lossy();
519 let mut md5 = Md5::new();
520 md5.update(microtime_str.as_bytes());
521 md5.update(pathname.as_bytes());
522 let hash = hex::encode(md5.finalize());
523 format!("{}/{}", date_str, hash)
524 }
525 };
526 self.hash_name = Some(hash_name);
527 }
528
529 let extension = match &self.extension {
531 Some(ext) => ext.clone(),
532 None => self.extension(),
533 };
534
535 let hash_name = self
536 .hash_name
537 .as_ref()
538 .expect("hash_name 已在上方初始化")
539 .clone();
540 if extension.is_empty() {
541 Ok(hash_name)
542 } else {
543 Ok(format!("{}.{}", hash_name, extension))
545 }
546 }
547
548 pub fn basename(&self) -> String {
550 self.path
551 .file_name()
552 .map(|n| n.to_string_lossy().to_string())
553 .unwrap_or_default()
554 }
555}
556
557#[derive(Debug, Clone)]
580pub struct UploadedFile {
581 file: File,
583 test: bool,
585 original_name: String,
587 mime_type: String,
589 error: UploadErrCode,
591}
592
593impl UploadedFile {
594 pub fn new<P: AsRef<Path>>(
596 path: P,
597 original_name: &str,
598 mime_type: Option<&str>,
599 error: Option<i32>,
600 test: bool,
601 ) -> Result<Self, UploadError> {
602 let error = UploadErrCode::from_i32(error.unwrap_or(0));
603 let mime = mime_type.unwrap_or("application/octet-stream").to_string();
604
605 let check_path = error == UploadErrCode::Ok;
607 let file = File::new(path, check_path)?;
608
609 Ok(Self {
610 file,
611 test,
612 original_name: original_name.to_string(),
613 mime_type: mime,
614 error,
615 })
616 }
617
618 pub fn is_valid(&self) -> bool {
631 let is_ok = self.error == UploadErrCode::Ok;
632 if self.test {
633 is_ok
634 } else {
635 is_ok && self.file.path().is_file()
637 }
638 }
639
640 pub fn move_to<P: AsRef<Path>>(
650 &mut self,
651 directory: P,
652 name: Option<&str>,
653 ) -> Result<File, UploadError> {
654 if !self.is_valid() {
655 return Err(UploadError::UploadFailed(
656 self.error.error_message().to_string(),
657 ));
658 }
659
660 if self.test {
661 return self.file.move_to(directory, name);
663 }
664
665 let target = self.file.get_target_file(directory.as_ref(), name)?;
668 fs::rename(self.file.path(), &target.path).map_err(|e| UploadError::MoveFailed {
669 from: self.file.path().to_string_lossy().to_string(),
670 to: target.path.to_string_lossy().to_string(),
671 error: e.to_string(),
672 })?;
673
674 #[cfg(unix)]
676 {
677 use std::os::unix::fs::PermissionsExt;
678 let _ = fs::set_permissions(&target.path, fs::Permissions::from_mode(0o666));
679 }
680
681 Ok(target)
682 }
683
684 pub fn original_mime(&self) -> &str {
686 &self.mime_type
687 }
688
689 pub fn original_name(&self) -> &str {
691 &self.original_name
692 }
693
694 pub fn original_extension(&self) -> String {
698 Path::new(&self.original_name)
699 .extension()
700 .map(|e| e.to_string_lossy().to_string())
701 .unwrap_or_default()
702 }
703
704 pub fn extension(&self) -> String {
713 self.original_extension()
714 }
715
716 pub fn error_message(&self) -> &'static str {
718 self.error.error_message()
719 }
720
721 pub fn error_code(&self) -> UploadErrCode {
723 self.error
724 }
725
726 pub fn as_file(&self) -> &File {
728 &self.file
729 }
730
731 pub fn as_file_mut(&mut self) -> &mut File {
733 &mut self.file
734 }
735}
736
737fn get_name(name: &str) -> Result<String, UploadError> {
753 if name.contains("..") {
755 return Err(UploadError::InvalidFileName(name.to_string()));
756 }
757 let original_name = name.replace('\\', "/");
759 match original_name.rfind('/') {
761 Some(pos) => Ok(original_name[pos + 1..].to_string()),
762 None => Ok(original_name),
763 }
764}
765
766fn compute_file_hash(path: &Path, algo: HashAlgo) -> Result<String, UploadError> {
768 let mut file = fs::File::open(path)?;
769 let mut buf = Vec::new();
770 file.read_to_end(&mut buf)?;
771
772 let hash = match algo {
773 HashAlgo::Md5 => {
774 let mut h = Md5::new();
775 h.update(&buf);
776 hex::encode(h.finalize())
777 }
778 HashAlgo::Sha1 => {
779 let mut h = sha1::Sha1::new();
780 h.update(&buf);
781 hex::encode(h.finalize())
782 }
783 HashAlgo::Sha256 => {
784 let mut h = sha2::Sha256::new();
785 h.update(&buf);
786 hex::encode(h.finalize())
787 }
788 HashAlgo::Sha512 => {
789 let mut h = sha2::Sha512::new();
790 h.update(&buf);
791 hex::encode(h.finalize())
792 }
793 };
794 Ok(hash)
795}
796
797use axum::extract::Multipart;
802use std::io::Write;
803use tempfile::NamedTempFile;
804
805#[derive(Debug, Default)]
807pub struct MultipartResult {
808 pub files: HashMap<String, Vec<UploadedFile>>,
812
813 pub fields: HashMap<String, String>,
817}
818
819impl MultipartResult {
820 pub fn file(&self, name: &str) -> Option<&UploadedFile> {
826 self.files.get(name).and_then(|list| list.first())
827 }
828
829 pub fn files(&self, name: &str) -> Option<&Vec<UploadedFile>> {
831 self.files.get(name)
832 }
833
834 pub fn field(&self, name: &str) -> Option<&str> {
836 self.fields.get(name).map(|s| s.as_str())
837 }
838
839 pub fn file_count(&self) -> usize {
841 self.files.values().map(|v| v.len()).sum()
842 }
843
844 pub fn is_empty(&self) -> bool {
846 self.files.is_empty() && self.fields.is_empty()
847 }
848}
849
850pub async fn parse_multipart(multipart: &mut Multipart) -> Result<MultipartResult, UploadError> {
880 let mut result = MultipartResult::default();
881
882 while let Some(field) = multipart
883 .next_field()
884 .await
885 .map_err(|e| UploadError::UploadFailed(e.to_string()))?
886 {
887 let name = field.name().unwrap_or("").to_string();
888 let file_name = field.file_name().map(|s| s.to_string());
889 let content_type = field.content_type().map(|s| s.to_string());
890
891 let data = field
892 .bytes()
893 .await
894 .map_err(|e| UploadError::UploadFailed(e.to_string()))?;
895
896 if let Some(file_name) = file_name {
897 let ext = Path::new(&file_name)
899 .extension()
900 .map(|e| format!(".{}", e.to_string_lossy()))
901 .unwrap_or_default();
902
903 let mut temp = NamedTempFile::with_suffix(&ext)?;
904 temp.write_all(&data)?;
905
906 let (_file, path) = temp.keep()?;
909
910 let uploaded = UploadedFile::new(
911 &path,
912 &file_name,
913 content_type.as_deref(),
914 Some(0),
915 true,
918 )?;
919
920 result.files.entry(name).or_default().push(uploaded);
921 } else {
922 let value = String::from_utf8_lossy(&data).to_string();
924 result.fields.insert(name, value);
925 }
926 }
927
928 Ok(result)
929}
930
931#[cfg(test)]
937mod tests {
938 use super::*;
939 use std::io::Write;
940 use tempfile::NamedTempFile;
941
942 fn create_temp_file(content: &[u8], suffix: &str) -> NamedTempFile {
944 let mut file = NamedTempFile::with_suffix(suffix).expect("创建临时文件失败");
945 file.write_all(content).expect("写入临时文件失败");
946 file
947 }
948
949 #[test]
954 fn test_upload_err_code_from_i32() {
955 assert_eq!(UploadErrCode::from_i32(0), UploadErrCode::Ok);
956 assert_eq!(UploadErrCode::from_i32(1), UploadErrCode::IniSize);
957 assert_eq!(UploadErrCode::from_i32(2), UploadErrCode::FormSize);
958 assert_eq!(UploadErrCode::from_i32(3), UploadErrCode::Partial);
959 assert_eq!(UploadErrCode::from_i32(4), UploadErrCode::NoFile);
960 assert_eq!(UploadErrCode::from_i32(6), UploadErrCode::NoTmpDir);
961 assert_eq!(UploadErrCode::from_i32(7), UploadErrCode::CantWrite);
962 assert_eq!(UploadErrCode::from_i32(99), UploadErrCode::Ok);
964 }
965
966 #[test]
967 fn test_upload_err_code_error_message() {
968 assert_eq!(
970 UploadErrCode::IniSize.error_message(),
971 "upload File size exceeds the maximum value"
972 );
973 assert_eq!(
974 UploadErrCode::FormSize.error_message(),
975 "upload File size exceeds the maximum value"
976 );
977 assert_eq!(
979 UploadErrCode::Partial.error_message(),
980 "only the portion of file is uploaded"
981 );
982 assert_eq!(UploadErrCode::NoFile.error_message(), "no file to uploaded");
984 assert_eq!(
986 UploadErrCode::NoTmpDir.error_message(),
987 "upload temp dir not found"
988 );
989 assert_eq!(UploadErrCode::CantWrite.error_message(), "file write error");
991 assert_eq!(UploadErrCode::Ok.error_message(), "unknown upload error");
993 }
994
995 #[test]
1000 fn test_hash_algo_as_str() {
1001 assert_eq!(HashAlgo::Md5.as_str(), "md5");
1002 assert_eq!(HashAlgo::Sha1.as_str(), "sha1");
1003 assert_eq!(HashAlgo::Sha256.as_str(), "sha256");
1004 assert_eq!(HashAlgo::Sha512.as_str(), "sha512");
1005 }
1006
1007 #[test]
1008 fn test_hash_algo_parse_algo() {
1009 assert_eq!(HashAlgo::parse_algo("md5"), Some(HashAlgo::Md5));
1010 assert_eq!(HashAlgo::parse_algo("sha1"), Some(HashAlgo::Sha1));
1011 assert_eq!(HashAlgo::parse_algo("sha256"), Some(HashAlgo::Sha256));
1012 assert_eq!(HashAlgo::parse_algo("sha512"), Some(HashAlgo::Sha512));
1013 assert_eq!(HashAlgo::parse_algo("unknown"), None);
1015 }
1016
1017 #[test]
1022 fn test_file_new_with_check_path() {
1023 let temp = create_temp_file(b"hello", ".txt");
1025 let file = File::new(temp.path(), true);
1026 assert!(file.is_ok());
1027
1028 let file = File::new("/nonexistent/file.txt", true);
1030 assert!(matches!(file, Err(UploadError::FileNotFound(_))));
1031 }
1032
1033 #[test]
1034 fn test_file_new_without_check_path() {
1035 let file = File::new("/nonexistent/file.txt", false);
1037 assert!(file.is_ok());
1038 }
1039
1040 #[test]
1041 fn test_file_new_unchecked() {
1042 let file = File::new_unchecked("/some/path/file.txt");
1043 assert_eq!(file.path(), Path::new("/some/path/file.txt"));
1044 }
1045
1046 #[test]
1047 fn test_file_path() {
1048 let temp = create_temp_file(b"hello", ".txt");
1049 let file = File::new(temp.path(), true).unwrap();
1050 assert_eq!(file.path(), temp.path());
1051 }
1052
1053 #[test]
1054 fn test_file_path_name() {
1055 let temp = create_temp_file(b"hello", ".txt");
1056 let file = File::new(temp.path(), true).unwrap();
1057 assert_eq!(file.path_name(), temp.path().to_string_lossy().to_string());
1058 }
1059
1060 #[test]
1061 fn test_file_extension() {
1062 let temp = create_temp_file(b"hello", ".txt");
1064 let file = File::new(temp.path(), true).unwrap();
1065 assert_eq!(file.extension(), "txt");
1066 }
1067
1068 #[test]
1069 fn test_file_extension_no_extension() {
1070 let mut file = NamedTempFile::new().unwrap();
1072 file.write_all(b"hello").unwrap();
1073 let file = File::new(file.path(), true).unwrap();
1074 assert_eq!(file.extension(), "");
1075 }
1076
1077 #[test]
1078 fn test_file_set_extension() {
1079 let temp = create_temp_file(b"hello", ".txt");
1081 let mut file = File::new(temp.path(), true).unwrap();
1082 assert_eq!(file.extension(), "txt");
1083 file.set_extension("jpg");
1084 assert_eq!(file.extension, Some("jpg".to_string()));
1085 }
1086
1087 #[test]
1088 fn test_file_basename() {
1089 let temp = create_temp_file(b"hello", ".txt");
1091 let file = File::new(temp.path(), true).unwrap();
1092 let basename = file.basename();
1093 assert!(basename.ends_with(".txt"));
1094 }
1095
1096 #[test]
1101 fn test_file_md5() {
1102 let temp = create_temp_file(b"hello", ".txt");
1104 let mut file = File::new(temp.path(), true).unwrap();
1105 let md5 = file.md5().unwrap();
1106 assert_eq!(md5, "5d41402abc4b2a76b9719d911017c592");
1108 }
1109
1110 #[test]
1111 fn test_file_sha1() {
1112 let temp = create_temp_file(b"hello", ".txt");
1114 let mut file = File::new(temp.path(), true).unwrap();
1115 let sha1 = file.sha1().unwrap();
1116 assert_eq!(sha1, "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d");
1118 }
1119
1120 #[test]
1121 fn test_file_hash_md5() {
1122 let temp = create_temp_file(b"hello", ".txt");
1124 let mut file = File::new(temp.path(), true).unwrap();
1125 let hash = file.hash(HashAlgo::Md5).unwrap();
1126 assert_eq!(hash, "5d41402abc4b2a76b9719d911017c592");
1127 }
1128
1129 #[test]
1130 fn test_file_hash_sha1() {
1131 let temp = create_temp_file(b"hello", ".txt");
1133 let mut file = File::new(temp.path(), true).unwrap();
1134 let hash = file.hash(HashAlgo::Sha1).unwrap();
1135 assert_eq!(hash, "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d");
1136 }
1137
1138 #[test]
1139 fn test_file_hash_sha256() {
1140 let temp = create_temp_file(b"hello", ".txt");
1142 let mut file = File::new(temp.path(), true).unwrap();
1143 let hash = file.hash(HashAlgo::Sha256).unwrap();
1144 assert_eq!(
1146 hash,
1147 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
1148 );
1149 }
1150
1151 #[test]
1152 fn test_file_hash_sha512() {
1153 let temp = create_temp_file(b"hello", ".txt");
1155 let mut file = File::new(temp.path(), true).unwrap();
1156 let hash = file.hash(HashAlgo::Sha512).unwrap();
1157 assert!(hash.starts_with("9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca"));
1159 }
1160
1161 #[test]
1162 fn test_file_hash_caching() {
1163 let temp = create_temp_file(b"hello", ".txt");
1165 let mut file = File::new(temp.path(), true).unwrap();
1166 let hash1 = file.hash(HashAlgo::Md5).unwrap();
1167 let hash2 = file.hash(HashAlgo::Md5).unwrap();
1168 assert_eq!(hash1, hash2);
1169 assert!(file.hash.contains_key("md5"));
1171 }
1172
1173 #[test]
1174 fn test_file_hash_multiple_algos() {
1175 let temp = create_temp_file(b"hello", ".txt");
1177 let mut file = File::new(temp.path(), true).unwrap();
1178 let md5 = file.hash(HashAlgo::Md5).unwrap();
1179 let sha1 = file.hash(HashAlgo::Sha1).unwrap();
1180 assert_ne!(md5, sha1);
1181 assert!(file.hash.contains_key("md5"));
1182 assert!(file.hash.contains_key("sha1"));
1183 }
1184
1185 #[test]
1190 fn test_file_get_mime_text() {
1191 let temp = create_temp_file(b"hello", ".txt");
1193 let file = File::new(temp.path(), true).unwrap();
1194 let mime = file.get_mime().unwrap();
1195 assert!(
1198 mime == "text/plain" || mime == "application/octet-stream",
1199 "mime = {}",
1200 mime
1201 );
1202 }
1203
1204 #[test]
1205 fn test_file_get_mime_png() {
1206 let png_header = [
1208 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, ];
1212 let mut file = NamedTempFile::with_suffix(".png").unwrap();
1213 file.write_all(&png_header).unwrap();
1214 let file = File::new(file.path(), true).unwrap();
1215 let mime = file.get_mime().unwrap();
1216 assert_eq!(mime, "image/png");
1217 }
1218
1219 #[test]
1220 fn test_file_get_mime_jpg() {
1221 let jpg_header = [0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, b'J', b'F', b'I', b'F'];
1223 let mut file = NamedTempFile::with_suffix(".jpg").unwrap();
1224 file.write_all(&jpg_header).unwrap();
1225 let file = File::new(file.path(), true).unwrap();
1226 let mime = file.get_mime().unwrap();
1227 assert_eq!(mime, "image/jpeg");
1228 }
1229
1230 #[test]
1231 fn test_file_get_mime_unknown_extension() {
1232 let temp = create_temp_file(&[0x00, 0x01, 0x02, 0x03], "");
1234 let file = File::new(temp.path(), true).unwrap();
1235 let mime = file.get_mime().unwrap();
1236 assert_eq!(mime, "application/octet-stream");
1237 }
1238
1239 #[test]
1244 fn test_file_move_with_default_name() {
1245 let temp = create_temp_file(b"hello", ".txt");
1247 let temp_dir = tempfile::tempdir().unwrap();
1248 let target_dir = temp_dir.path().join("subdir");
1249
1250 let mut file = File::new(temp.path(), true).unwrap();
1251 let original_basename = file.basename();
1252 let moved = file.move_to(&target_dir, None).unwrap();
1253
1254 assert!(moved.path().is_file());
1255 assert_eq!(moved.basename(), original_basename);
1256 assert!(!temp.path().exists());
1258 }
1259
1260 #[test]
1261 fn test_file_move_with_custom_name() {
1262 let temp = create_temp_file(b"hello", ".txt");
1264 let temp_dir = tempfile::tempdir().unwrap();
1265
1266 let mut file = File::new(temp.path(), true).unwrap();
1267 let moved = file.move_to(&temp_dir, Some("custom.txt")).unwrap();
1268
1269 assert!(moved.path().is_file());
1270 assert_eq!(moved.basename(), "custom.txt");
1271 }
1272
1273 #[test]
1274 fn test_file_move_creates_directory() {
1275 let temp = create_temp_file(b"hello", ".txt");
1277 let temp_dir = tempfile::tempdir().unwrap();
1278 let nested_dir = temp_dir.path().join("a").join("b").join("c");
1279
1280 let mut file = File::new(temp.path(), true).unwrap();
1281 let moved = file.move_to(&nested_dir, Some("file.txt")).unwrap();
1282
1283 assert!(moved.path().is_file());
1284 assert!(nested_dir.is_dir());
1285 }
1286
1287 #[test]
1288 fn test_file_move_preserves_content() {
1289 let temp = create_temp_file(b"hello world", ".txt");
1290 let temp_dir = tempfile::tempdir().unwrap();
1291
1292 let mut file = File::new(temp.path(), true).unwrap();
1293 let moved = file.move_to(&temp_dir, Some("moved.txt")).unwrap();
1294
1295 let content = std::fs::read_to_string(moved.path()).unwrap();
1296 assert_eq!(content, "hello world");
1297 }
1298
1299 #[test]
1304 fn test_file_hash_name_default_format() {
1305 let temp = create_temp_file(b"hello", ".txt");
1307 let mut file = File::new(temp.path(), true).unwrap();
1308 let hash_name = file.hash_name(HashNameRule::Default).unwrap();
1309
1310 let parts: Vec<&str> = hash_name.split('/').collect();
1312 assert_eq!(parts.len(), 2);
1313 let (date_part, md5_ext) = (parts[0], parts[1]);
1314
1315 assert_eq!(date_part.len(), 8);
1317 assert!(date_part.chars().all(|c| c.is_ascii_digit()));
1318
1319 let ext_parts: Vec<&str> = md5_ext.split('.').collect();
1321 assert_eq!(ext_parts.len(), 2);
1322 assert_eq!(ext_parts[0].len(), 32); assert!(ext_parts[0].chars().all(|c| c.is_ascii_hexdigit()));
1324 assert_eq!(ext_parts[1], "txt"); }
1326
1327 #[test]
1328 fn test_file_hash_name_default_no_extension() {
1329 let temp = NamedTempFile::new().unwrap();
1331 std::fs::write(temp.path(), b"hello").unwrap();
1332 let mut file = File::new(temp.path(), true).unwrap();
1333 let hash_name = file.hash_name(HashNameRule::Default).unwrap();
1334
1335 let parts: Vec<&str> = hash_name.split('/').collect();
1337 assert_eq!(parts.len(), 2);
1338 assert!(!parts[1].contains('.'));
1339 }
1340
1341 #[test]
1342 fn test_file_hash_name_hash_md5() {
1343 let temp = create_temp_file(b"hello", ".txt");
1345 let mut file = File::new(temp.path(), true).unwrap();
1346 let hash_name = file.hash_name(HashNameRule::Hash(HashAlgo::Md5)).unwrap();
1347
1348 assert_eq!(hash_name, "5d/41402abc4b2a76b9719d911017c592.txt");
1350 }
1351
1352 #[test]
1353 fn test_file_hash_name_hash_sha1() {
1354 let temp = create_temp_file(b"hello", ".txt");
1356 let mut file = File::new(temp.path(), true).unwrap();
1357 let hash_name = file.hash_name(HashNameRule::Hash(HashAlgo::Sha1)).unwrap();
1358
1359 assert_eq!(hash_name, "aa/f4c61ddcc5e8a2dabede0f3b482cd9aea9434d.txt");
1361 }
1362
1363 #[test]
1364 fn test_file_hash_name_caching() {
1365 let temp = create_temp_file(b"hello", ".txt");
1367 let mut file = File::new(temp.path(), true).unwrap();
1368 let name1 = file.hash_name(HashNameRule::Default).unwrap();
1369 let name2 = file.hash_name(HashNameRule::Default).unwrap();
1370 assert_eq!(name1, name2);
1371 }
1372
1373 #[test]
1374 fn test_file_hash_name_with_set_extension() {
1375 let temp = create_temp_file(b"hello", ".txt");
1378 let mut file = File::new(temp.path(), true).unwrap();
1379 file.set_extension("jpg");
1380 let hash_name = file.hash_name(HashNameRule::Hash(HashAlgo::Md5)).unwrap();
1381 assert!(hash_name.ends_with(".jpg"));
1382 }
1383
1384 #[test]
1389 fn test_uploaded_file_new_ok() {
1390 let temp = create_temp_file(b"hello", ".txt");
1392 let uploaded = UploadedFile::new(
1393 temp.path(),
1394 "original.txt",
1395 Some("text/plain"),
1396 Some(0),
1397 false,
1398 );
1399 assert!(uploaded.is_ok());
1400 }
1401
1402 #[test]
1403 fn test_uploaded_file_new_with_error() {
1404 let uploaded = UploadedFile::new("/nonexistent", "original.txt", None, Some(3), false);
1406 assert!(uploaded.is_ok());
1407 }
1408
1409 #[test]
1410 fn test_uploaded_file_new_check_path_on_ok() {
1411 let uploaded = UploadedFile::new("/nonexistent", "original.txt", None, Some(0), false);
1413 assert!(matches!(uploaded, Err(UploadError::FileNotFound(_))));
1414 }
1415
1416 #[test]
1417 fn test_uploaded_file_original_name() {
1418 let temp = create_temp_file(b"hello", ".txt");
1419 let uploaded = UploadedFile::new(
1420 temp.path(),
1421 "my_file.txt",
1422 Some("text/plain"),
1423 Some(0),
1424 false,
1425 )
1426 .unwrap();
1427 assert_eq!(uploaded.original_name(), "my_file.txt");
1428 }
1429
1430 #[test]
1431 fn test_uploaded_file_original_mime() {
1432 let temp = create_temp_file(b"hello", ".txt");
1433 let uploaded = UploadedFile::new(
1434 temp.path(),
1435 "my_file.txt",
1436 Some("text/plain"),
1437 Some(0),
1438 false,
1439 )
1440 .unwrap();
1441 assert_eq!(uploaded.original_mime(), "text/plain");
1442 }
1443
1444 #[test]
1445 fn test_uploaded_file_original_mime_default() {
1446 let temp = create_temp_file(b"hello", ".txt");
1448 let uploaded = UploadedFile::new(temp.path(), "my_file.txt", None, Some(0), false).unwrap();
1449 assert_eq!(uploaded.original_mime(), "application/octet-stream");
1450 }
1451
1452 #[test]
1453 fn test_uploaded_file_original_extension() {
1454 let temp = create_temp_file(b"hello", ".txt");
1456 let uploaded = UploadedFile::new(
1457 temp.path(),
1458 "my_file.txt",
1459 Some("text/plain"),
1460 Some(0),
1461 false,
1462 )
1463 .unwrap();
1464 assert_eq!(uploaded.original_extension(), "txt");
1465 }
1466
1467 #[test]
1468 fn test_uploaded_file_original_extension_no_ext() {
1469 let temp = create_temp_file(b"hello", ".txt");
1470 let uploaded = UploadedFile::new(
1471 temp.path(),
1472 "no_extension",
1473 Some("text/plain"),
1474 Some(0),
1475 false,
1476 )
1477 .unwrap();
1478 assert_eq!(uploaded.original_extension(), "");
1479 }
1480
1481 #[test]
1482 fn test_uploaded_file_extension_overrides_parent() {
1483 let temp = create_temp_file(b"hello", ".txt");
1485 let uploaded =
1487 UploadedFile::new(temp.path(), "photo.jpg", Some("image/jpeg"), Some(0), false)
1488 .unwrap();
1489 assert_eq!(uploaded.extension(), "jpg");
1491 assert_eq!(uploaded.as_file().extension(), "txt");
1493 }
1494
1495 #[test]
1500 fn test_uploaded_file_is_valid_ok() {
1501 let temp = create_temp_file(b"hello", ".txt");
1503 let uploaded = UploadedFile::new(
1504 temp.path(),
1505 "my_file.txt",
1506 Some("text/plain"),
1507 Some(0),
1508 false,
1509 )
1510 .unwrap();
1511 assert!(uploaded.is_valid());
1513 }
1514
1515 #[test]
1516 fn test_uploaded_file_is_valid_with_error() {
1517 let temp = create_temp_file(b"hello", ".txt");
1518 let uploaded = UploadedFile::new(
1519 temp.path(),
1520 "my_file.txt",
1521 Some("text/plain"),
1522 Some(3),
1523 false,
1524 )
1525 .unwrap();
1526 assert!(!uploaded.is_valid());
1528 }
1529
1530 #[test]
1531 fn test_uploaded_file_is_valid_test_mode() {
1532 let temp = create_temp_file(b"hello", ".txt");
1535 let uploaded = UploadedFile::new(
1536 temp.path(),
1537 "my_file.txt",
1538 Some("text/plain"),
1539 Some(0),
1540 true,
1541 )
1542 .unwrap();
1543 assert!(uploaded.is_valid());
1544 }
1545
1546 #[test]
1547 fn test_uploaded_file_is_valid_test_mode_with_error() {
1548 let uploaded = UploadedFile::new(
1549 "/nonexistent",
1550 "my_file.txt",
1551 Some("text/plain"),
1552 Some(4),
1553 true,
1554 )
1555 .unwrap();
1556 assert!(!uploaded.is_valid());
1557 }
1558
1559 #[test]
1564 fn test_uploaded_file_move_test_mode() {
1565 let temp = create_temp_file(b"hello", ".txt");
1567 let temp_dir = tempfile::tempdir().unwrap();
1568 let mut uploaded = UploadedFile::new(
1569 temp.path(),
1570 "original.txt",
1571 Some("text/plain"),
1572 Some(0),
1573 true,
1574 )
1575 .unwrap();
1576 let moved = uploaded.move_to(&temp_dir, Some("moved.txt")).unwrap();
1577 assert!(moved.path().is_file());
1578 assert_eq!(moved.basename(), "moved.txt");
1579 }
1580
1581 #[test]
1582 fn test_uploaded_file_move_invalid() {
1583 let temp = create_temp_file(b"hello", ".txt");
1585 let temp_dir = tempfile::tempdir().unwrap();
1586 let mut uploaded = UploadedFile::new(
1587 temp.path(),
1588 "original.txt",
1589 Some("text/plain"),
1590 Some(3),
1591 false,
1592 )
1593 .unwrap();
1594 let result = uploaded.move_to(&temp_dir, Some("moved.txt"));
1595 assert!(matches!(result, Err(UploadError::UploadFailed(_))));
1596 if let Err(UploadError::UploadFailed(msg)) = result {
1598 assert_eq!(msg, "only the portion of file is uploaded");
1599 }
1600 }
1601
1602 #[test]
1603 fn test_uploaded_file_move_real() {
1604 let temp = create_temp_file(b"hello", ".txt");
1606 let temp_dir = tempfile::tempdir().unwrap();
1607 let mut uploaded = UploadedFile::new(
1608 temp.path(),
1609 "original.txt",
1610 Some("text/plain"),
1611 Some(0),
1612 false,
1613 )
1614 .unwrap();
1615 let moved = uploaded.move_to(&temp_dir, Some("uploaded.txt")).unwrap();
1616 assert!(moved.path().is_file());
1617 assert_eq!(moved.basename(), "uploaded.txt");
1618 assert!(!temp.path().exists());
1620 }
1621
1622 #[test]
1627 fn test_uploaded_file_error_message() {
1628 let temp = create_temp_file(b"hello", ".txt");
1630
1631 let uploaded_ok = UploadedFile::new(temp.path(), "f.txt", None, Some(0), false).unwrap();
1632 assert_eq!(uploaded_ok.error_message(), "unknown upload error");
1633
1634 let uploaded_1 = UploadedFile::new(temp.path(), "f.txt", None, Some(1), false).unwrap();
1635 assert_eq!(
1636 uploaded_1.error_message(),
1637 "upload File size exceeds the maximum value"
1638 );
1639
1640 let uploaded_3 = UploadedFile::new(temp.path(), "f.txt", None, Some(3), false).unwrap();
1641 assert_eq!(
1642 uploaded_3.error_message(),
1643 "only the portion of file is uploaded"
1644 );
1645
1646 let uploaded_4 = UploadedFile::new(temp.path(), "f.txt", None, Some(4), false).unwrap();
1647 assert_eq!(uploaded_4.error_message(), "no file to uploaded");
1648 }
1649
1650 #[test]
1651 fn test_uploaded_file_error_code() {
1652 let temp = create_temp_file(b"hello", ".txt");
1653 let uploaded = UploadedFile::new(temp.path(), "f.txt", None, Some(7), false).unwrap();
1654 assert_eq!(uploaded.error_code(), UploadErrCode::CantWrite);
1655 }
1656
1657 #[test]
1662 fn test_get_name_simple() {
1663 assert_eq!(get_name("file.txt").unwrap(), "file.txt");
1665 }
1666
1667 #[test]
1668 fn test_get_name_with_path() {
1669 assert_eq!(get_name("/path/to/file.txt").unwrap(), "file.txt");
1671 }
1672
1673 #[test]
1674 fn test_get_name_with_backslash() {
1675 assert_eq!(get_name("\\path\\to\\file.txt").unwrap(), "file.txt");
1677 }
1678
1679 #[test]
1680 fn test_get_name_mixed_separators() {
1681 assert_eq!(get_name("\\path/to\\file.txt").unwrap(), "file.txt");
1683 }
1684
1685 #[test]
1686 fn test_get_name_only_filename() {
1687 assert_eq!(get_name("filename").unwrap(), "filename");
1688 }
1689
1690 #[test]
1691 fn test_get_name_rejects_path_traversal() {
1692 assert!(get_name("../etc/passwd").is_err());
1694 assert!(get_name("file..txt").is_err());
1695 assert!(get_name("..hidden").is_err());
1696 }
1697
1698 #[test]
1703 fn test_php_behavior_hash_name_md5_split() {
1704 let temp = create_temp_file(b"hello", "");
1708 let mut file = File::new(temp.path(), true).unwrap();
1709 let hash_name = file.hash_name(HashNameRule::Hash(HashAlgo::Md5)).unwrap();
1710 assert_eq!(hash_name, "5d/41402abc4b2a76b9719d911017c592");
1711 }
1712
1713 #[test]
1714 fn test_php_behavior_hash_name_sha1_split() {
1715 let temp = create_temp_file(b"hello", "");
1719 let mut file = File::new(temp.path(), true).unwrap();
1720 let hash_name = file.hash_name(HashNameRule::Hash(HashAlgo::Sha1)).unwrap();
1721 assert_eq!(hash_name, "aa/f4c61ddcc5e8a2dabede0f3b482cd9aea9434d");
1722 }
1723
1724 #[test]
1725 fn test_php_behavior_hash_name_default_format() {
1726 let temp = create_temp_file(b"hello", ".txt");
1728 let mut file = File::new(temp.path(), true).unwrap();
1729 let hash_name = file.hash_name(HashNameRule::Default).unwrap();
1730
1731 let re = regex::Regex::new(r"^\d{8}/[0-9a-f]{32}\.txt$").unwrap();
1733 assert!(
1734 re.is_match(&hash_name),
1735 "hash_name 格式不匹配:{}",
1736 hash_name
1737 );
1738 }
1739
1740 #[test]
1741 fn test_php_behavior_uploaded_file_extension_override() {
1742 let temp = create_temp_file(b"hello", ".txt");
1744 let uploaded =
1745 UploadedFile::new(temp.path(), "photo.jpg", Some("image/jpeg"), Some(0), false)
1746 .unwrap();
1747 assert_eq!(uploaded.extension(), "jpg");
1749 assert_eq!(uploaded.as_file().extension(), "txt");
1751 }
1752
1753 #[test]
1754 fn test_php_behavior_is_valid_test_mode() {
1755 let temp = create_temp_file(b"hello", ".txt");
1757 let uploaded = UploadedFile::new(temp.path(), "f.txt", None, Some(0), true).unwrap();
1758 assert!(uploaded.is_valid());
1759 }
1760
1761 #[test]
1762 fn test_php_behavior_is_valid_non_test_mode_requires_file() {
1763 let temp = create_temp_file(b"hello", ".txt");
1766 let uploaded = UploadedFile::new(temp.path(), "f.txt", None, Some(0), false).unwrap();
1767 assert!(uploaded.is_valid());
1768
1769 let result = UploadedFile::new("/nonexistent/path", "f.txt", None, Some(0), false);
1771 assert!(matches!(result, Err(UploadError::FileNotFound(_))));
1772 }
1773
1774 #[test]
1775 fn test_php_behavior_error_message_mapping() {
1776 let temp = create_temp_file(b"hello", ".txt");
1778 assert_eq!(
1779 UploadedFile::new(temp.path(), "f", None, Some(1), false)
1780 .unwrap()
1781 .error_message(),
1782 "upload File size exceeds the maximum value"
1783 );
1784 assert_eq!(
1785 UploadedFile::new(temp.path(), "f", None, Some(2), false)
1786 .unwrap()
1787 .error_message(),
1788 "upload File size exceeds the maximum value"
1789 );
1790 assert_eq!(
1791 UploadedFile::new(temp.path(), "f", None, Some(3), false)
1792 .unwrap()
1793 .error_message(),
1794 "only the portion of file is uploaded"
1795 );
1796 assert_eq!(
1797 UploadedFile::new(temp.path(), "f", None, Some(4), false)
1798 .unwrap()
1799 .error_message(),
1800 "no file to uploaded"
1801 );
1802 assert_eq!(
1803 UploadedFile::new(temp.path(), "f", None, Some(6), false)
1804 .unwrap()
1805 .error_message(),
1806 "upload temp dir not found"
1807 );
1808 assert_eq!(
1809 UploadedFile::new(temp.path(), "f", None, Some(7), false)
1810 .unwrap()
1811 .error_message(),
1812 "file write error"
1813 );
1814 assert_eq!(
1815 UploadedFile::new(temp.path(), "f", None, Some(0), false)
1816 .unwrap()
1817 .error_message(),
1818 "unknown upload error"
1819 );
1820 }
1821
1822 #[test]
1823 fn test_php_behavior_move_creates_directory() {
1824 let temp = create_temp_file(b"hello", ".txt");
1826 let temp_dir = tempfile::tempdir().unwrap();
1827 let nested = temp_dir.path().join("a").join("b").join("c");
1828
1829 let mut file = File::new(temp.path(), true).unwrap();
1830 let moved = file.move_to(&nested, Some("file.txt")).unwrap();
1831
1832 assert!(moved.path().is_file());
1833 assert!(nested.is_dir());
1834 }
1835
1836 #[test]
1837 fn test_php_behavior_move_chmod_unix() {
1838 let temp = create_temp_file(b"hello", ".txt");
1841 let temp_dir = tempfile::tempdir().unwrap();
1842
1843 let mut file = File::new(temp.path(), true).unwrap();
1844 let moved = file.move_to(&temp_dir, Some("file.txt")).unwrap();
1845
1846 #[cfg(unix)]
1847 {
1848 use std::os::unix::fs::PermissionsExt;
1849 let perms = std::fs::metadata(moved.path())
1850 .unwrap()
1851 .permissions()
1852 .mode();
1853 assert_eq!(perms & 0o777, 0o644);
1855 }
1856 #[cfg(not(unix))]
1857 {
1858 let _ = moved;
1859 }
1860 }
1861
1862 #[test]
1863 fn test_php_behavior_hash_caching() {
1864 let temp = create_temp_file(b"hello", ".txt");
1866 let mut file = File::new(temp.path(), true).unwrap();
1867 let md5_1 = file.hash(HashAlgo::Md5).unwrap();
1868 let md5_2 = file.hash(HashAlgo::Md5).unwrap();
1870 assert_eq!(md5_1, md5_2);
1871 }
1872
1873 #[test]
1874 fn test_php_behavior_hash_name_caching() {
1875 let temp = create_temp_file(b"hello", ".txt");
1877 let mut file = File::new(temp.path(), true).unwrap();
1878 let name_1 = file.hash_name(HashNameRule::Hash(HashAlgo::Md5)).unwrap();
1879 let name_2 = file.hash_name(HashNameRule::Hash(HashAlgo::Sha1)).unwrap();
1880 assert_eq!(name_1, name_2);
1882 }
1883
1884 #[test]
1885 fn test_php_behavior_get_mime_infer() {
1886 let png_header = [
1889 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48,
1890 0x44, 0x52,
1891 ];
1892 let mut file = NamedTempFile::with_suffix(".png").unwrap();
1893 file.write_all(&png_header).unwrap();
1894 let file = File::new(file.path(), true).unwrap();
1895 assert_eq!(file.get_mime().unwrap(), "image/png");
1896 }
1897
1898 #[test]
1899 fn test_php_behavior_uploaded_file_move_test_uses_rename() {
1900 let temp = create_temp_file(b"hello", ".txt");
1902 let temp_dir = tempfile::tempdir().unwrap();
1903 let mut uploaded =
1904 UploadedFile::new(temp.path(), "original.txt", None, Some(0), true).unwrap();
1905 let moved = uploaded.move_to(&temp_dir, Some("moved.txt")).unwrap();
1906 assert!(moved.path().is_file());
1907 assert!(!temp.path().exists());
1909 }
1910
1911 #[test]
1912 fn test_php_behavior_uploaded_file_move_non_test_uses_move_uploaded_file() {
1913 let temp = create_temp_file(b"hello", ".txt");
1915 let temp_dir = tempfile::tempdir().unwrap();
1916 let mut uploaded =
1917 UploadedFile::new(temp.path(), "original.txt", None, Some(0), false).unwrap();
1918 let moved = uploaded.move_to(&temp_dir, Some("moved.txt")).unwrap();
1919 assert!(moved.path().is_file());
1920 assert!(!temp.path().exists());
1921 }
1922
1923 #[test]
1924 fn test_uploaded_file_as_file_access() {
1925 let temp = create_temp_file(b"hello", ".txt");
1926 let uploaded = UploadedFile::new(
1927 temp.path(),
1928 "original.txt",
1929 Some("text/plain"),
1930 Some(0),
1931 false,
1932 )
1933 .unwrap();
1934 assert_eq!(uploaded.as_file().path(), temp.path());
1936 assert_eq!(uploaded.as_file().extension(), "txt");
1937 }
1938
1939 #[test]
1940 fn test_uploaded_file_as_file_mut_access() {
1941 let temp = create_temp_file(b"hello", ".txt");
1942 let mut uploaded = UploadedFile::new(
1943 temp.path(),
1944 "original.txt",
1945 Some("text/plain"),
1946 Some(0),
1947 false,
1948 )
1949 .unwrap();
1950 let md5 = uploaded.as_file_mut().md5().unwrap();
1952 assert_eq!(md5, "5d41402abc4b2a76b9719d911017c592");
1953 }
1954}