![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/domains/lavocat.quebec/private_html/src/utils/ |
import { File } from 'formidable';
import { createHash } from 'crypto';
import { promises as fs } from 'fs';
import path from 'path';
// Constants for validation
export const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100MB
export const ALLOWED_FILE_TYPES = [
'application/pdf',
'image/jpeg',
'image/png',
'image/gif',
'video/mp4',
'video/mpeg',
'video/x-mpeg',
'video/x-mpeg4',
'video/webm',
'video/quicktime', // mov
'video/x-msvideo', // avi
'video/x-m4v', // m4v
'video/3gpp', // 3gp
'video/x-flv', // flv
'video/x-matroska', // mkv
'video/x-ms-wmv' // wmv
];
// File type signatures (magic numbers)
const FILE_SIGNATURES: { [key: string]: number[][] } = {
'application/pdf': [[0x25, 0x50, 0x44, 0x46]], // %PDF
'image/jpeg': [[0xFF, 0xD8, 0xFF]], // JPEG
'image/png': [[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]], // PNG
'image/gif': [[0x47, 0x49, 0x46, 0x38]] // GIF
// No signature check for video for now
};
export interface FileValidationResult {
isValid: boolean;
error?: string;
sanitizedFilename?: string;
}
export async function validateFile(file: File): Promise<FileValidationResult> {
try {
// Check file size
if (file.size > MAX_FILE_SIZE) {
return {
isValid: false,
error: `File size exceeds maximum limit of ${MAX_FILE_SIZE / (1024 * 1024)}MB
};
}
// Check if file type is allowed
if (!file.mimetype || !ALLOWED_FILE_TYPES.includes(file.mimetype)) {
return {
isValid: false,
error: 'File type not allowed'
};
}
// Only check signature for non-video files
if (!file.mimetype.startsWith('video/')) {
const buffer = await fs.readFile(file.filepath);
const signature = Array.from(buffer.slice(0, 8));
const expectedSignature = FILE_SIGNATURES[file.mimetype];
if (!expectedSignature) {
return {
isValid: false,
error: 'Unknown file type'
};
}
const isValidSignature = expectedSignature.some(sig =>
sig.every((byte, index) => signature[index] === byte)
);
if (!isValidSignature) {
return {
isValid: false,
error: 'File signature does not match declared type'
};
}
}
// Generate safe filename
const sanitizedFilename = generateSafeFilename(file.originalFilename || '');
return {
isValid: true,
sanitizedFilename
};
} catch (error) {
return {
isValid: false,
error: 'Error validating file'
};
}
}
function generateSafeFilename(originalFilename: string): string {
// Remove any directory traversal attempts
const basename = path.basename(originalFilename);
// Remove any non-alphanumeric characters except for . and -
const sanitized = basename.replace(/[^a-zA-Z0-9.-]/g, '_');
// Generate a unique hash
const hash = createHash('md5')
.update(originalFilename + Date.now().toString())
.digest('hex')
.slice(0, 8);
// Get the file extension
const ext = path.extname(sanitized);
// Combine sanitized name with hash and extension
return `${sanitized.slice(0, -ext.length)}_${hash}${ext}
}
export async function cleanupFile(filepath: string): Promise<void> {
try {
await fs.unlink(filepath);
} catch (error) {
throw new Error('Failed to cleanup file');
}
}