scriptguard-library
Version:
A secure and customizable text input field library for React.
54 lines (44 loc) • 1.77 kB
text/typescript
import { MAGIC_NUMBERS, readMagicNumber } from "./magicNumbers";
import { sanitizeFileName } from "./sanitizeFileName";
export async function validateFile(
file: File,
allowedTypes?: string[],
maxFileSizeMB?: number
): Promise<string | null> {
const fileName = sanitizeFileName(file.name);
const fileType = file.type;
const fileSizeMB = file.size / (1024 * 1024);
const nameParts = fileName.split(".");
const extension = nameParts.pop()?.toLowerCase();
// 1. Double extensions
if (nameParts.length > 1) {
return "Suspicious file name detected (multiple extensions).";
}
// 2. Forbidden characters
const forbiddenChars = /[\\/:*?"<>|]/;
if (forbiddenChars.test(fileName)) {
return "Filename contains forbidden characters.";
}
// 3. Size check
if (maxFileSizeMB && fileSizeMB > maxFileSizeMB) {
return `File size exceeds limit (${maxFileSizeMB}MB).`;
}
// 4. Dangerous extensions
const dangerousExtensions = ["php", "exe", "js", "sh", "bat", "cmd"];
if (extension && dangerousExtensions.includes(extension)) {
return `Dangerous file extension detected (.${extension}).`;
}
// 5. Allowed MIME type check
if (allowedTypes && !allowedTypes.includes(fileType)) {
return `File type not allowed. Allowed types: ${allowedTypes.join(", ")}`;
}
// 6. Magic number check
const expectedMagic = MAGIC_NUMBERS[fileType];
if (expectedMagic) {
const magic = await readMagicNumber(file, expectedMagic.length / 2); // 2 hex chars per byte
if (!magic.startsWith(expectedMagic)) {
return `File content does not match expected type (${fileType}).`;
}
}
return null; // All checks passed
}