Skip to main content

reinhardt_forms/fields/
file_field.rs

1use crate::field::{FieldError, FieldResult, FormField, Widget};
2
3/// Default maximum file size: 10 MB
4const DEFAULT_FILE_MAX_SIZE: u64 = 10 * 1024 * 1024;
5
6/// Default maximum image file size: 5 MB
7const DEFAULT_IMAGE_MAX_SIZE: u64 = 5 * 1024 * 1024;
8
9/// Validate that a filename does not contain path traversal sequences,
10/// null bytes, or represent an absolute path.
11fn validate_filename_safety(filename: &str) -> FieldResult<()> {
12	// Reject null bytes
13	if filename.contains('\0') {
14		return Err(FieldError::Validation(
15			"Filename contains null bytes".to_string(),
16		));
17	}
18
19	// Reject directory traversal by checking path components split on both
20	// '/' and '\\' separators.  This catches bare ".." without a trailing
21	// slash as well as sequences like "../" and "..\\".
22	for component in filename.split(['/', '\\']) {
23		if component == ".." {
24			return Err(FieldError::Validation(
25				"Filename contains directory traversal sequence".to_string(),
26			));
27		}
28	}
29
30	// Reject absolute paths (Unix-style)
31	if filename.starts_with('/') {
32		return Err(FieldError::Validation(
33			"Filename must not be an absolute path".to_string(),
34		));
35	}
36
37	// Reject absolute paths (Windows UNC paths, e.g. \\server\share)
38	if filename.starts_with("\\\\") {
39		return Err(FieldError::Validation(
40			"Filename must not be an absolute path".to_string(),
41		));
42	}
43
44	// Reject absolute paths (Windows-style drive letters, e.g. C:\)
45	let bytes = filename.as_bytes();
46	if bytes.len() >= 3
47		&& bytes[0].is_ascii_alphabetic()
48		&& bytes[1] == b':'
49		&& (bytes[2] == b'\\' || bytes[2] == b'/')
50	{
51		return Err(FieldError::Validation(
52			"Filename must not be an absolute path".to_string(),
53		));
54	}
55
56	Ok(())
57}
58
59/// FileField for file upload
60pub struct FileField {
61	/// The field name used as the form data key.
62	pub name: String,
63	/// Optional human-readable label for display.
64	pub label: Option<String>,
65	/// Whether a file must be uploaded.
66	pub required: bool,
67	/// Optional help text displayed alongside the field.
68	pub help_text: Option<String>,
69	/// The widget type used for rendering this field.
70	pub widget: Widget,
71	/// Optional initial (default) value for the field.
72	pub initial: Option<serde_json::Value>,
73	/// Maximum allowed filename length.
74	pub max_length: Option<usize>,
75	/// Whether to accept empty (zero-byte) files.
76	pub allow_empty_file: bool,
77	/// Maximum file size in bytes. Defaults to 10 MB.
78	pub max_size: u64,
79}
80
81impl FileField {
82	/// Create a new FileField
83	///
84	/// # Examples
85	///
86	/// ```
87	/// use reinhardt_forms::fields::FileField;
88	///
89	/// let field = FileField::new("upload".to_string());
90	/// assert_eq!(field.name, "upload");
91	/// assert_eq!(field.max_size, 10 * 1024 * 1024);
92	/// ```
93	pub fn new(name: String) -> Self {
94		Self {
95			name,
96			label: None,
97			required: true,
98			help_text: None,
99			widget: Widget::FileInput,
100			initial: None,
101			max_length: None,
102			allow_empty_file: false,
103			max_size: DEFAULT_FILE_MAX_SIZE,
104		}
105	}
106
107	/// Set the maximum file size in bytes.
108	///
109	/// # Examples
110	///
111	/// ```
112	/// use reinhardt_forms::fields::FileField;
113	///
114	/// let field = FileField::new("upload".to_string()).with_max_size(5 * 1024 * 1024);
115	/// assert_eq!(field.max_size, 5 * 1024 * 1024);
116	/// ```
117	pub fn with_max_size(mut self, max_size: u64) -> Self {
118		self.max_size = max_size;
119		self
120	}
121}
122
123impl FormField for FileField {
124	fn name(&self) -> &str {
125		&self.name
126	}
127
128	fn label(&self) -> Option<&str> {
129		self.label.as_deref()
130	}
131
132	fn required(&self) -> bool {
133		self.required
134	}
135
136	fn help_text(&self) -> Option<&str> {
137		self.help_text.as_deref()
138	}
139
140	fn widget(&self) -> &Widget {
141		&self.widget
142	}
143
144	fn initial(&self) -> Option<&serde_json::Value> {
145		self.initial.as_ref()
146	}
147
148	fn clean(&self, value: Option<&serde_json::Value>) -> FieldResult<serde_json::Value> {
149		match value {
150			None if self.required => Err(FieldError::required(None)),
151			None => Ok(serde_json::Value::Null),
152			Some(v) => {
153				// Expect an object with filename and optional size
154				let obj = v
155					.as_object()
156					.ok_or_else(|| FieldError::Invalid("Expected object".to_string()))?;
157
158				let filename = obj
159					.get("filename")
160					.and_then(|f| f.as_str())
161					.ok_or_else(|| FieldError::Invalid("Missing filename".to_string()))?;
162
163				if filename.is_empty() {
164					if self.required {
165						return Err(FieldError::required(None));
166					}
167					return Ok(serde_json::Value::Null);
168				}
169
170				// Validate filename for path traversal attacks
171				validate_filename_safety(filename)?;
172
173				// Check filename length
174				if let Some(max) = self.max_length
175					&& filename.len() > max
176				{
177					return Err(FieldError::Validation(format!(
178						"Filename is too long (max {} characters)",
179						max
180					)));
181				}
182
183				// Check file size limit before further processing
184				if let Some(size) = obj.get("size").and_then(|s| s.as_u64()) {
185					if size > self.max_size {
186						return Err(FieldError::Validation(format!(
187							"File size {} bytes exceeds maximum allowed size of {} bytes",
188							size, self.max_size
189						)));
190					}
191
192					// Check for empty file
193					if !self.allow_empty_file && size == 0 {
194						return Err(FieldError::Validation(
195							"The submitted file is empty".to_string(),
196						));
197					}
198				} else if !self.allow_empty_file {
199					// No size field present and empty files not allowed
200					return Err(FieldError::Validation(
201						"The submitted file is empty".to_string(),
202					));
203				}
204
205				Ok(v.clone())
206			}
207		}
208	}
209}
210
211/// ImageField for image upload with additional validation
212pub struct ImageField {
213	/// The field name used as the form data key.
214	pub name: String,
215	/// Optional human-readable label for display.
216	pub label: Option<String>,
217	/// Whether an image must be uploaded.
218	pub required: bool,
219	/// Optional help text displayed alongside the field.
220	pub help_text: Option<String>,
221	/// The widget type used for rendering this field.
222	pub widget: Widget,
223	/// Optional initial (default) value for the field.
224	pub initial: Option<serde_json::Value>,
225	/// Maximum allowed filename length.
226	pub max_length: Option<usize>,
227	/// Whether to accept empty (zero-byte) files.
228	pub allow_empty_file: bool,
229	/// Maximum file size in bytes. Defaults to 5 MB.
230	pub max_size: u64,
231}
232
233impl ImageField {
234	/// Create a new ImageField
235	///
236	/// # Examples
237	///
238	/// ```
239	/// use reinhardt_forms::fields::ImageField;
240	///
241	/// let field = ImageField::new("photo".to_string());
242	/// assert_eq!(field.name, "photo");
243	/// assert_eq!(field.max_size, 5 * 1024 * 1024);
244	/// ```
245	pub fn new(name: String) -> Self {
246		Self {
247			name,
248			label: None,
249			required: true,
250			help_text: None,
251			widget: Widget::FileInput,
252			initial: None,
253			max_length: None,
254			allow_empty_file: false,
255			max_size: DEFAULT_IMAGE_MAX_SIZE,
256		}
257	}
258
259	/// Set the maximum file size in bytes.
260	///
261	/// # Examples
262	///
263	/// ```
264	/// use reinhardt_forms::fields::ImageField;
265	///
266	/// let field = ImageField::new("photo".to_string()).with_max_size(2 * 1024 * 1024);
267	/// assert_eq!(field.max_size, 2 * 1024 * 1024);
268	/// ```
269	pub fn with_max_size(mut self, max_size: u64) -> Self {
270		self.max_size = max_size;
271		self
272	}
273
274	fn is_valid_image_extension(filename: &str) -> bool {
275		// NOTE: SVG is intentionally excluded due to Stored XSS risk.
276		// SVG files can contain arbitrary JavaScript that executes when served
277		// with Content-Type: image/svg+xml. Use opt-in validation if SVG support
278		// is required, with appropriate sanitization or Content-Disposition headers.
279		let valid_extensions = ["jpg", "jpeg", "png", "gif", "webp", "bmp"];
280		filename
281			.rsplit('.')
282			.next()
283			.map(|ext| valid_extensions.contains(&ext.to_lowercase().as_str()))
284			.unwrap_or(false)
285	}
286}
287
288impl FormField for ImageField {
289	fn name(&self) -> &str {
290		&self.name
291	}
292
293	fn label(&self) -> Option<&str> {
294		self.label.as_deref()
295	}
296
297	fn required(&self) -> bool {
298		self.required
299	}
300
301	fn help_text(&self) -> Option<&str> {
302		self.help_text.as_deref()
303	}
304
305	fn widget(&self) -> &Widget {
306		&self.widget
307	}
308
309	fn initial(&self) -> Option<&serde_json::Value> {
310		self.initial.as_ref()
311	}
312
313	fn clean(&self, value: Option<&serde_json::Value>) -> FieldResult<serde_json::Value> {
314		match value {
315			None if self.required => Err(FieldError::required(None)),
316			None => Ok(serde_json::Value::Null),
317			Some(v) => {
318				let obj = v
319					.as_object()
320					.ok_or_else(|| FieldError::Invalid("Expected object".to_string()))?;
321
322				let filename = obj
323					.get("filename")
324					.and_then(|f| f.as_str())
325					.ok_or_else(|| FieldError::Invalid("Missing filename".to_string()))?;
326
327				if filename.is_empty() {
328					if self.required {
329						return Err(FieldError::required(None));
330					}
331					return Ok(serde_json::Value::Null);
332				}
333
334				// Validate filename for path traversal attacks
335				validate_filename_safety(filename)?;
336
337				// Validate image extension
338				if !Self::is_valid_image_extension(filename) {
339					return Err(FieldError::Validation(
340						"Upload a valid image. The file you uploaded was either not an image or a corrupted image".to_string(),
341					));
342				}
343
344				// Check filename length
345				if let Some(max) = self.max_length
346					&& filename.len() > max
347				{
348					return Err(FieldError::Validation(format!(
349						"Filename is too long (max {} characters)",
350						max
351					)));
352				}
353
354				// Check file size limit before further processing
355				if let Some(size) = obj.get("size").and_then(|s| s.as_u64()) {
356					if size > self.max_size {
357						return Err(FieldError::Validation(format!(
358							"File size {} bytes exceeds maximum allowed size of {} bytes",
359							size, self.max_size
360						)));
361					}
362
363					// Check for empty file
364					if !self.allow_empty_file && size == 0 {
365						return Err(FieldError::Validation(
366							"The submitted file is empty".to_string(),
367						));
368					}
369				} else if !self.allow_empty_file {
370					// No size field present and empty files not allowed
371					return Err(FieldError::Validation(
372						"The submitted file is empty".to_string(),
373					));
374				}
375
376				Ok(v.clone())
377			}
378		}
379	}
380}
381
382#[cfg(test)]
383mod tests {
384	use super::*;
385	use rstest::rstest;
386
387	// =========================================================================
388	// FileField Tests
389	// =========================================================================
390
391	// ---- Happy Path ----
392
393	#[rstest]
394	fn test_filefield_valid() {
395		// Arrange
396		let field = FileField::new("document".to_string());
397		let file = serde_json::json!({
398			"filename": "test.pdf",
399			"size": 1024
400		});
401
402		// Act
403		let result = field.clean(Some(&file));
404
405		// Assert
406		assert!(result.is_ok());
407	}
408
409	#[rstest]
410	fn test_filefield_default_max_size() {
411		// Arrange & Act
412		let field = FileField::new("document".to_string());
413
414		// Assert
415		assert_eq!(field.max_size, 10 * 1024 * 1024);
416	}
417
418	#[rstest]
419	fn test_filefield_custom_max_size() {
420		// Arrange & Act
421		let field = FileField::new("document".to_string()).with_max_size(5 * 1024 * 1024);
422
423		// Assert
424		assert_eq!(field.max_size, 5 * 1024 * 1024);
425	}
426
427	#[rstest]
428	fn test_filefield_within_size_limit() {
429		// Arrange
430		let field = FileField::new("document".to_string()).with_max_size(1024);
431		let file = serde_json::json!({
432			"filename": "test.pdf",
433			"size": 1024
434		});
435
436		// Act
437		let result = field.clean(Some(&file));
438
439		// Assert
440		assert!(result.is_ok());
441	}
442
443	// ---- Error Cases ----
444
445	#[rstest]
446	fn test_filefield_exceeds_size_limit() {
447		// Arrange
448		let field = FileField::new("document".to_string()).with_max_size(1024);
449		let file = serde_json::json!({
450			"filename": "test.pdf",
451			"size": 1025
452		});
453
454		// Act
455		let result = field.clean(Some(&file));
456
457		// Assert
458		assert!(
459			matches!(result, Err(FieldError::Validation(ref msg)) if msg.contains("exceeds maximum"))
460		);
461	}
462
463	#[rstest]
464	fn test_filefield_exceeds_default_size_limit() {
465		// Arrange
466		let field = FileField::new("document".to_string());
467		let over_10mb = 10 * 1024 * 1024 + 1;
468		let file = serde_json::json!({
469			"filename": "huge.bin",
470			"size": over_10mb
471		});
472
473		// Act
474		let result = field.clean(Some(&file));
475
476		// Assert
477		assert!(
478			matches!(result, Err(FieldError::Validation(ref msg)) if msg.contains("exceeds maximum"))
479		);
480	}
481
482	#[rstest]
483	fn test_filefield_empty() {
484		// Arrange
485		let field = FileField::new("document".to_string());
486		let file = serde_json::json!({
487			"filename": "test.pdf",
488			"size": 0
489		});
490
491		// Act & Assert
492		assert!(matches!(
493			field.clean(Some(&file)),
494			Err(FieldError::Validation(_))
495		));
496	}
497
498	#[rstest]
499	fn test_filefield_no_size_field_rejects_when_empty_not_allowed() {
500		// Arrange
501		let field = FileField::new("document".to_string());
502		let file = serde_json::json!({
503			"filename": "test.pdf"
504		});
505
506		// Act
507		let result = field.clean(Some(&file));
508
509		// Assert
510		assert!(matches!(result, Err(FieldError::Validation(ref msg)) if msg.contains("empty")));
511	}
512
513	// ---- Path Traversal Security ----
514
515	#[rstest]
516	#[case("../../etc/passwd", "directory traversal")]
517	#[case("../secret.txt", "directory traversal")]
518	#[case("foo/../../etc/shadow", "directory traversal")]
519	#[case("..\\windows\\system32", "directory traversal")]
520	#[case("..\\..\\boot.ini", "directory traversal")]
521	fn test_filefield_rejects_directory_traversal(
522		#[case] filename: &str,
523		#[case] expected_msg: &str,
524	) {
525		// Arrange
526		let field = FileField::new("document".to_string());
527		let file = serde_json::json!({
528			"filename": filename,
529			"size": 1024
530		});
531
532		// Act
533		let result = field.clean(Some(&file));
534
535		// Assert
536		assert!(
537			matches!(result, Err(FieldError::Validation(ref msg)) if msg.contains(expected_msg)),
538			"Expected directory traversal rejection for filename: {filename}"
539		);
540	}
541
542	#[rstest]
543	fn test_filefield_rejects_null_bytes() {
544		// Arrange
545		let field = FileField::new("document".to_string());
546		let file = serde_json::json!({
547			"filename": "file\0name.pdf",
548			"size": 1024
549		});
550
551		// Act
552		let result = field.clean(Some(&file));
553
554		// Assert
555		assert!(
556			matches!(result, Err(FieldError::Validation(ref msg)) if msg.contains("null bytes")),
557			"Expected null bytes rejection"
558		);
559	}
560
561	#[rstest]
562	#[case("/etc/passwd")]
563	#[case("/var/log/syslog")]
564	fn test_filefield_rejects_unix_absolute_path(#[case] filename: &str) {
565		// Arrange
566		let field = FileField::new("document".to_string());
567		let file = serde_json::json!({
568			"filename": filename,
569			"size": 1024
570		});
571
572		// Act
573		let result = field.clean(Some(&file));
574
575		// Assert
576		assert!(
577			matches!(result, Err(FieldError::Validation(ref msg)) if msg.contains("absolute path")),
578			"Expected absolute path rejection for filename: {filename}"
579		);
580	}
581
582	#[rstest]
583	#[case("C:\\Windows\\system32\\config")]
584	#[case("D:/Documents/secret.txt")]
585	fn test_filefield_rejects_windows_absolute_path(#[case] filename: &str) {
586		// Arrange
587		let field = FileField::new("document".to_string());
588		let file = serde_json::json!({
589			"filename": filename,
590			"size": 1024
591		});
592
593		// Act
594		let result = field.clean(Some(&file));
595
596		// Assert
597		assert!(
598			matches!(result, Err(FieldError::Validation(ref msg)) if msg.contains("absolute path")),
599			"Expected absolute path rejection for filename: {filename}"
600		);
601	}
602
603	#[rstest]
604	#[case("document.pdf")]
605	#[case("my-file_v2.tar.gz")]
606	#[case("photo (1).jpg")]
607	fn test_filefield_accepts_safe_filenames(#[case] filename: &str) {
608		// Arrange
609		let field = FileField::new("document".to_string());
610		let file = serde_json::json!({
611			"filename": filename,
612			"size": 1024
613		});
614
615		// Act
616		let result = field.clean(Some(&file));
617
618		// Assert
619		assert!(
620			result.is_ok(),
621			"Expected safe filename to be accepted: {filename}"
622		);
623	}
624
625	// ---- Boundary Value Analysis ----
626
627	#[rstest]
628	#[case(1023, true)] // max_size - 1
629	#[case(1024, true)] // max_size (boundary)
630	#[case(1025, false)] // max_size + 1
631	fn test_filefield_size_boundary(#[case] size: u64, #[case] valid: bool) {
632		// Arrange
633		let field = FileField::new("document".to_string()).with_max_size(1024);
634		let file = serde_json::json!({
635			"filename": "test.pdf",
636			"size": size
637		});
638
639		// Act & Assert
640		assert_eq!(field.clean(Some(&file)).is_ok(), valid);
641	}
642
643	// ---- Decision Table ----
644
645	#[rstest]
646	#[case(1024, 512, true)] // max_size=1024, size=512 -> OK
647	#[case(1024, 1024, true)] // max_size=1024, size=1024 -> OK (at limit)
648	#[case(1024, 2048, false)] // max_size=1024, size=2048 -> Error
649	#[case(0, 1, false)] // max_size=0, size=1 -> Error (zero tolerance)
650	fn test_filefield_size_decision_table(
651		#[case] max_size: u64,
652		#[case] file_size: u64,
653		#[case] expected_ok: bool,
654	) {
655		// Arrange
656		let field = FileField::new("document".to_string()).with_max_size(max_size);
657		let file = serde_json::json!({
658			"filename": "test.pdf",
659			"size": file_size
660		});
661
662		// Act & Assert
663		assert_eq!(field.clean(Some(&file)).is_ok(), expected_ok);
664	}
665
666	// =========================================================================
667	// ImageField Tests
668	// =========================================================================
669
670	// ---- Happy Path ----
671
672	#[rstest]
673	fn test_imagefield_valid() {
674		// Arrange
675		let field = ImageField::new("photo".to_string());
676		let file = serde_json::json!({
677			"filename": "test.jpg",
678			"size": 1024
679		});
680
681		// Act & Assert
682		assert!(field.clean(Some(&file)).is_ok());
683	}
684
685	#[rstest]
686	fn test_imagefield_default_max_size() {
687		// Arrange & Act
688		let field = ImageField::new("photo".to_string());
689
690		// Assert
691		assert_eq!(field.max_size, 5 * 1024 * 1024);
692	}
693
694	#[rstest]
695	fn test_imagefield_custom_max_size() {
696		// Arrange & Act
697		let field = ImageField::new("photo".to_string()).with_max_size(2 * 1024 * 1024);
698
699		// Assert
700		assert_eq!(field.max_size, 2 * 1024 * 1024);
701	}
702
703	// ---- Error Cases ----
704
705	#[rstest]
706	fn test_imagefield_invalid_extension() {
707		// Arrange
708		let field = ImageField::new("photo".to_string());
709		let file = serde_json::json!({
710			"filename": "test.pdf",
711			"size": 1024
712		});
713
714		// Act & Assert
715		assert!(matches!(
716			field.clean(Some(&file)),
717			Err(FieldError::Validation(_))
718		));
719	}
720
721	#[rstest]
722	fn test_imagefield_rejects_svg_for_xss_prevention() {
723		// Arrange
724		let field = ImageField::new("photo".to_string());
725		// SVG files are rejected due to Stored XSS vulnerability risk
726		let svg_file = serde_json::json!({
727			"filename": "malicious.svg",
728			"size": 1024
729		});
730
731		// Act & Assert
732		assert!(
733			matches!(field.clean(Some(&svg_file)), Err(FieldError::Validation(_))),
734			"SVG files should be rejected to prevent Stored XSS attacks"
735		);
736	}
737
738	#[rstest]
739	fn test_imagefield_exceeds_size_limit() {
740		// Arrange
741		let field = ImageField::new("photo".to_string()).with_max_size(1024);
742		let file = serde_json::json!({
743			"filename": "large.jpg",
744			"size": 1025
745		});
746
747		// Act
748		let result = field.clean(Some(&file));
749
750		// Assert
751		assert!(
752			matches!(result, Err(FieldError::Validation(ref msg)) if msg.contains("exceeds maximum"))
753		);
754	}
755
756	#[rstest]
757	fn test_imagefield_exceeds_default_size_limit() {
758		// Arrange
759		let field = ImageField::new("photo".to_string());
760		let over_5mb = 5 * 1024 * 1024 + 1;
761		let file = serde_json::json!({
762			"filename": "huge.png",
763			"size": over_5mb
764		});
765
766		// Act
767		let result = field.clean(Some(&file));
768
769		// Assert
770		assert!(
771			matches!(result, Err(FieldError::Validation(ref msg)) if msg.contains("exceeds maximum"))
772		);
773	}
774
775	// ---- Path Traversal Security (ImageField) ----
776
777	#[rstest]
778	fn test_imagefield_rejects_directory_traversal() {
779		// Arrange
780		let field = ImageField::new("photo".to_string());
781		let file = serde_json::json!({
782			"filename": "../../etc/passwd.jpg",
783			"size": 1024
784		});
785
786		// Act
787		let result = field.clean(Some(&file));
788
789		// Assert
790		assert!(
791			matches!(result, Err(FieldError::Validation(ref msg)) if msg.contains("directory traversal")),
792			"Expected directory traversal rejection for ImageField"
793		);
794	}
795
796	#[rstest]
797	fn test_imagefield_rejects_null_bytes() {
798		// Arrange
799		let field = ImageField::new("photo".to_string());
800		let file = serde_json::json!({
801			"filename": "photo\0.jpg",
802			"size": 1024
803		});
804
805		// Act
806		let result = field.clean(Some(&file));
807
808		// Assert
809		assert!(
810			matches!(result, Err(FieldError::Validation(ref msg)) if msg.contains("null bytes")),
811			"Expected null bytes rejection for ImageField"
812		);
813	}
814
815	#[rstest]
816	fn test_imagefield_rejects_absolute_path() {
817		// Arrange
818		let field = ImageField::new("photo".to_string());
819		let file = serde_json::json!({
820			"filename": "/etc/photo.jpg",
821			"size": 1024
822		});
823
824		// Act
825		let result = field.clean(Some(&file));
826
827		// Assert
828		assert!(
829			matches!(result, Err(FieldError::Validation(ref msg)) if msg.contains("absolute path")),
830			"Expected absolute path rejection for ImageField"
831		);
832	}
833
834	// ---- Boundary Value Analysis ----
835
836	#[rstest]
837	#[case(2047, true)] // max_size - 1
838	#[case(2048, true)] // max_size (boundary)
839	#[case(2049, false)] // max_size + 1
840	fn test_imagefield_size_boundary(#[case] size: u64, #[case] valid: bool) {
841		// Arrange
842		let field = ImageField::new("photo".to_string()).with_max_size(2048);
843		let file = serde_json::json!({
844			"filename": "photo.jpg",
845			"size": size
846		});
847
848		// Act & Assert
849		assert_eq!(field.clean(Some(&file)).is_ok(), valid);
850	}
851}