@@ -11,6 +11,12 @@ export type FileTypeValidatorOptions = {
1111 * @default false
1212 */
1313 skipMagicNumbersValidation ?: boolean ;
14+
15+ /**
16+ * If `true`, and magic number check fails, fallback to mimetype comparison.
17+ * @default false
18+ */
19+ fallbackToMimetype ?: boolean ;
1420} ;
1521
1622/**
@@ -27,10 +33,26 @@ export class FileTypeValidator extends FileValidator<
2733 IFile
2834> {
2935 buildErrorMessage ( file ?: IFile ) : string {
36+ const expected = this . validationOptions . fileType ;
37+
3038 if ( file ?. mimetype ) {
31- return `Validation failed (current file type is ${ file . mimetype } , expected type is ${ this . validationOptions . fileType } )` ;
39+ const baseMessage = `Validation failed (current file type is ${ file . mimetype } , expected type is ${ expected } )` ;
40+
41+ /**
42+ * If fallbackToMimetype is enabled, this means the validator failed to detect the file type
43+ * via magic number inspection (e.g. due to an unknown or too short buffer),
44+ * and instead used the mimetype string provided by the client as a fallback.
45+ *
46+ * This message clarifies that fallback logic was used, in case users rely on file signatures.
47+ */
48+ if ( this . validationOptions . fallbackToMimetype ) {
49+ return `${ baseMessage } - magic number detection failed, used mimetype fallback` ;
50+ }
51+
52+ return baseMessage ;
3253 }
33- return `Validation failed (expected type is ${ this . validationOptions . fileType } )` ;
54+
55+ return `Validation failed (expected type is ${ expected } )` ;
3456 }
3557
3658 async isValid ( file ?: IFile ) : Promise < boolean > {
@@ -40,25 +62,34 @@ export class FileTypeValidator extends FileValidator<
4062
4163 const isFileValid = ! ! file && 'mimetype' in file ;
4264
65+ // Skip magic number validation if set
4366 if ( this . validationOptions . skipMagicNumbersValidation ) {
4467 return (
4568 isFileValid && ! ! file . mimetype . match ( this . validationOptions . fileType )
4669 ) ;
4770 }
4871
49- if ( ! isFileValid || ! file . buffer ) {
50- return false ;
51- }
72+ if ( ! isFileValid || ! file . buffer ) return false ;
5273
5374 try {
5475 const { fileTypeFromBuffer } =
5576 await loadEsm < typeof import ( 'file-type' ) > ( 'file-type' ) ;
56-
5777 const fileType = await fileTypeFromBuffer ( file . buffer ) ;
5878
59- return (
60- ! ! fileType && ! ! fileType . mime . match ( this . validationOptions . fileType )
61- ) ;
79+ if ( fileType ) {
80+ // Match detected mime type against allowed type
81+ return ! ! fileType . mime . match ( this . validationOptions . fileType ) ;
82+ }
83+
84+ /**
85+ * Fallback logic: If file-type cannot detect magic number (e.g. file too small),
86+ * Optionally fall back to mimetype string for compatibility.
87+ * This is useful for plain text, CSVs, or files without recognizable signatures.
88+ */
89+ if ( this . validationOptions . fallbackToMimetype ) {
90+ return ! ! file . mimetype . match ( this . validationOptions . fileType ) ;
91+ }
92+ return false ;
6293 } catch {
6394 return false ;
6495 }
0 commit comments