/** * Validate that uploaded file is a legitimate PDF * Checks magic bytes, not just MIME type */ export async function validatePdfFile(file: File): Promise<{ valid: boolean; error?: string }> { // Check file size first (quick rejection) const maxFileSizeKB = parseInt(process.env.MAX_PROOF_OF_PAYMENT_UPLOAD_SIZE_KB || '1024', 10); const maxFileSizeBytes = maxFileSizeKB * 1024; if (file.size === 0) { return { valid: false, error: 'File is empty' }; } if (file.size > maxFileSizeBytes) { return { valid: false, error: `File size exceeds ${maxFileSizeKB} KB limit` }; } // Read file content const arrayBuffer = await file.arrayBuffer(); const buffer = Buffer.from(arrayBuffer); // Check PDF magic bytes (header signature) // PDF files must start with "%PDF-" (bytes: 25 50 44 46 2D) const header = buffer.toString('utf-8', 0, 5); if (!header.startsWith('%PDF-')) { return { valid: false, error: 'Invalid PDF file format' }; } // Optional: Check for PDF version (1.0 to 2.0) const version = buffer.toString('utf-8', 5, 8); // e.g., "1.4", "1.7", "2.0" const versionMatch = version.match(/^(\d+\.\d+)/); if (!versionMatch) { return { valid: false, error: 'Invalid PDF version' }; } // Optional: Verify PDF EOF marker (should end with %%EOF) // Note: Some PDFs have trailing data, so this is lenient const endSection = buffer.toString('utf-8', Math.max(0, buffer.length - 1024)); if (!endSection.includes('%%EOF')) { console.warn('PDF missing EOF marker - may be corrupted'); // Don't reject, just warn (some valid PDFs have non-standard endings) } return { valid: true }; }