@@ -8,22 +8,95 @@ const AUDIO_MIMES = new Set([
88 'audio/ogg;codecs=opus' ,
99] ) ;
1010
11+ const ALLOWED_MIME_PREFIXES = new Set ( [
12+ 'image/' ,
13+ 'audio/' ,
14+ 'text/' ,
15+ 'application/pdf' ,
16+ 'application/json' ,
17+ 'application/vnd.ms-excel' ,
18+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.' ,
19+ 'application/msword' ,
20+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.' ,
21+ 'application/rtf' ,
22+ ] ) ;
23+
24+ const BLOCKED_MIME_SUBSTRINGS = [
25+ 'application/x-msdownload' ,
26+ 'application/x-msi' ,
27+ 'application/x-executable' ,
28+ 'application/x-sh' ,
29+ 'application/x-shellscript' ,
30+ 'application/javascript' ,
31+ 'text/javascript' ,
32+ 'application/x-bat' ,
33+ 'application/x-csh' ,
34+ 'application/vnd.microsoft.portable-executable' ,
35+ ] ;
36+
37+ const MIME_TO_EXT : Record < string , string > = {
38+ 'audio/webm' : 'webm' ,
39+ 'audio/ogg' : 'ogg' ,
40+ 'audio/mp4' : 'm4a' ,
41+ 'application/pdf' : 'pdf' ,
42+ 'application/json' : 'json' ,
43+ 'text/plain' : 'txt' ,
44+ 'text/csv' : 'csv' ,
45+ 'text/markdown' : 'md' ,
46+ 'text/html' : 'html' ,
47+ 'application/rtf' : 'rtf' ,
48+ 'application/msword' : 'doc' ,
49+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' : 'docx' ,
50+ 'application/vnd.ms-excel' : 'xls' ,
51+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' : 'xlsx' ,
52+ } ;
53+
1154export type MultipartFileResult = { mimetype : string ; toBuffer : ( ) => Promise < Buffer > } | undefined ;
1255
56+ function isAllowedMimetype ( mimetype : string ) : boolean {
57+ const normalized = mimetype . split ( ';' ) [ 0 ] . trim ( ) . toLowerCase ( ) ;
58+ if ( BLOCKED_MIME_SUBSTRINGS . some ( ( s ) => normalized . includes ( s ) ) ) return false ;
59+ if ( AUDIO_MIMES . has ( mimetype ) || normalized . startsWith ( 'audio/' ) ) return true ;
60+ if ( normalized . startsWith ( 'image/' ) ) return true ;
61+ if ( normalized . startsWith ( 'text/' ) ) return true ;
62+ for ( const prefix of ALLOWED_MIME_PREFIXES ) {
63+ if ( prefix . endsWith ( '.' ) && normalized . startsWith ( prefix ) ) return true ;
64+ if ( normalized === prefix || normalized . startsWith ( prefix ) ) return true ;
65+ }
66+ return false ;
67+ }
68+
1369export function validateUploadMimetype ( mimetype : string ) : void {
14- if ( ! AUDIO_MIMES . has ( mimetype ) && ! mimetype . startsWith ( 'audio/' ) ) {
15- throw new BadRequestException ( 'Unsupported file type' ) ;
70+ if ( ! mimetype || ! isAllowedMimetype ( mimetype ) ) {
71+ throw new BadRequestException ( 'Unsupported or blocked file type' ) ;
72+ }
73+ }
74+
75+ export function extFromMimetype ( mimetype : string ) : string {
76+ const normalized = mimetype . split ( ';' ) [ 0 ] . trim ( ) . toLowerCase ( ) ;
77+ const exact = MIME_TO_EXT [ normalized ] ;
78+ if ( exact ) return exact ;
79+ if ( normalized . startsWith ( 'image/' ) ) {
80+ const sub = normalized . replace ( 'image/' , '' ) ;
81+ return sub === 'jpeg' ? 'jpg' : sub ;
82+ }
83+ if ( normalized . startsWith ( 'audio/' ) ) {
84+ if ( normalized . includes ( 'webm' ) ) return 'webm' ;
85+ if ( normalized . includes ( 'ogg' ) ) return 'ogg' ;
86+ if ( normalized . includes ( 'mp4' ) ) return 'm4a' ;
87+ return 'webm' ;
1688 }
89+ return 'bin' ;
1790}
1891
1992export async function processUploadFile (
2093 fileResult : MultipartFileResult ,
21- saveAudioFromBuffer : ( buffer : Buffer , mimetype : string ) => string | Promise < string >
94+ saveFromBuffer : ( buffer : Buffer , mimetype : string ) => string | Promise < string >
2295) : Promise < { filename : string } > {
2396 if ( ! fileResult ) throw new BadRequestException ( 'No file uploaded' ) ;
24- const mimetype = fileResult . mimetype ?? 'audio/webm ' ;
97+ const mimetype = fileResult . mimetype ?? 'application/octet-stream ' ;
2598 validateUploadMimetype ( mimetype ) ;
2699 const buffer = await fileResult . toBuffer ( ) ;
27- const filename = await saveAudioFromBuffer ( buffer , mimetype ) ;
100+ const filename = await saveFromBuffer ( buffer , mimetype ) ;
28101 return { filename } ;
29102}
0 commit comments