javascript 47 lines · 7 steps

Validating file uploads by content, not just claims

Layered upload validation that checks size, extension, declared type, and the file's real magic bytes.

Explained by highlit
1const MAX_FILE_SIZE = 5 * 1024 * 1024;
2 
3const ALLOWED_TYPES = {
4 'image/jpeg': ['jpg', 'jpeg'],
5 'image/png': ['png'],
6 'image/webp': ['webp'],
7 'application/pdf': ['pdf'],
8};
9 
10const MAGIC_SIGNATURES = [
11 { type: 'image/jpeg', bytes: [0xff, 0xd8, 0xff] },
12 { type: 'image/png', bytes: [0x89, 0x50, 0x4e, 0x47] },
13 { type: 'image/webp', bytes: [0x52, 0x49, 0x46, 0x46] },
14 { type: 'application/pdf', bytes: [0x25, 0x50, 0x44, 0x46] },
15];
16 
17function sniffMimeType(buffer) {
18 return MAGIC_SIGNATURES.find(({ bytes }) =>
19 bytes.every((b, i) => buffer[i] === b)
20 )?.type ?? null;
21}
22 
23export function validateUpload(file) {
24 const errors = [];
25 
26 if (file.size === 0) {
27 errors.push('File is empty.');
28 } else if (file.size > MAX_FILE_SIZE) {
29 errors.push(`File exceeds the ${MAX_FILE_SIZE / 1024 / 1024}MB limit.`);
30 }
31 
32 const extension = file.originalname.split('.').pop()?.toLowerCase() ?? '';
33 const allowedExtensions = ALLOWED_TYPES[file.mimetype];
34 
35 if (!allowedExtensions) {
36 errors.push(`Unsupported file type: ${file.mimetype}.`);
37 } else if (!allowedExtensions.includes(extension)) {
38 errors.push(`Extension ".${extension}" does not match ${file.mimetype}.`);
39 }
40 
41 const detected = sniffMimeType(file.buffer);
42 if (detected && detected !== file.mimetype) {
43 errors.push(`Declared type ${file.mimetype} does not match contents (${detected}).`);
44 }
45 
46 return { valid: errors.length === 0, errors };
47}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Never trust a client-declared MIME type; verify it against the file's actual byte signature.
  2. 2Collecting errors into an array lets you report every problem at once instead of failing on the first.
  3. 3Cross-checking extension, declared type, and content closes gaps any single check would leave open.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Validating file uploads by content, not just claims — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code